// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Threading; namespace OpenTap.Diagnostic { /// /// Default timestamper. /// [Display("Local and Accurate", "Delivers timestamping that will be accurate to the system time over longer durations.")] public class AccurateStamper : ILogTimestampProvider { /// Prints a friendly name. /// public override string ToString() => "Local and Accurate"; long ILogTimestampProvider.Timestamp() { return DateTime.UtcNow.Ticks; } Stopwatch sw = Stopwatch.StartNew(); long UtcOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow).Ticks; const long ticksPerSecond = 10000000; // TimeSpan.FromSeconds(1).Ticks; long ILogTimestampProvider.ConvertToTicks(long timestamp) { if (sw.ElapsedTicks > ticksPerSecond) { UtcOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow).Ticks; sw.Restart(); } return (timestamp + UtcOffset); } } internal class LogContext : ILogContext, ILogContext2, IDisposable { readonly LogQueue LogQueue = new LogQueue(); public bool HasListeners => listeners.Count > 0; readonly List listeners = new List(); long processedMessages; long OutstandingMessages => LogQueue.PostedMessages - processedMessages; ILogTimestampProvider timeStamper = new AccurateStamper(); readonly AutoResetEvent flushBarrier = new AutoResetEvent(false); readonly Thread processor; public LogContext(bool startProcessor = true) { if (startProcessor) { processor = new Thread(ProcessLog) { IsBackground = true, Name = "Log processing" }; processor.Start(); } } static LogContext EmptyLogContext() { return new LogContext(false); } readonly AutoResetEvent newEventOccured = new AutoResetEvent(false); void ProcessLog() { var copy = new List(); Event[] bunch = new Event[0]; while (isDisposed == false) { newEventOccured.WaitOne(); flushBarrier.WaitOne(100); // let things queue up unless flush is called. int count = LogQueue.DequeueBunch(ref bunch); if (count > 0) { lock (listeners) { copy.Clear(); foreach (var thing in listeners) copy.Add(thing); } if (copy.Count > 0) { if (timeStamper != null) for (int i = 0; i < bunch.Length; i++) bunch[i].Timestamp = timeStamper.ConvertToTicks(bunch[i].Timestamp); foreach (var listener in copy) { try { using (var events = new EventCollection(bunch)) { listener.EventsLogged(events); } } catch { // ignored } } } processedMessages += bunch.LongLength; } } } public ILog CreateLog(string source) { return new Log(this, source); } public void RemoveLog(ILog logSource) { if (logSource is Log log) log.Context = EmptyLogContext(); } public void AttachListener(ILogListener listener) { lock (listeners) listeners.Add(listener); } public void DetachListener(ILogListener listener) { lock (listeners) listeners.Remove(listener); } public ReadOnlyCollection GetListeners() { return new ReadOnlyCollection(listeners); } public bool Flush(int timeoutMs = 0) { if (isDisposed) return true; long posted = LogQueue.PostedMessages; flushBarrier.Set(); newEventOccured.Set(); if (timeoutMs == 0) { while (((processedMessages - posted)) < 0) { Thread.Yield(); newEventOccured.Set(); flushBarrier.Set(); } return true; } { var sw = Stopwatch.StartNew(); while ((processedMessages - posted) < 0 && sw.ElapsedMilliseconds < timeoutMs) { Thread.Yield(); newEventOccured.Set(); flushBarrier.Set(); } return (processedMessages - posted) < 0; } } public bool Flush(TimeSpan timeout) { return Flush((int)timeout.TotalMilliseconds); } bool isDisposed; public void Dispose() { Flush(); isDisposed = true; newEventOccured.Set(); flushBarrier.Set(); } public bool Async { get; set; } public int MessageBufferSize { get; set; } public ILogTimestampProvider Timestamper { get { return timeStamper; } set { timeStamper = value; } } void injectEvent(Event @event) { lock (listeners) { using (EventCollection eventCollection = new EventCollection(new [] { @event })) { listeners.ForEach(l => l.EventsLogged(eventCollection)); } } } public void AddEvent(Event evt) { if (Async) { int msgCnt = MessageBufferSize; if (msgCnt > 0) { while (OutstandingMessages > msgCnt) Thread.Sleep(1); } LogQueue.Enqueue(evt); } else { injectEvent(evt); } this.newEventOccured.Set(); } internal class LogInjector { internal LogContext Context; public LogInjector(LogContext context) => Context = context; public void logEvent(Event evt) => Context.AddEvent(evt); public void LogEvent(string source, int eventType, string message) { if (Context.HasListeners) { long timestamp = getTimestamp(); var evt = new Event(0, eventType, message, source, timestamp); logEvent(evt); } } public void LogEvent(string source, int eventType, string message, params object[] args) { if (message == null) throw new ArgumentNullException(nameof(message)); if (args == null) throw new ArgumentNullException(nameof(args)); if (Context.HasListeners) { var messageFmt = string.Format(message, args); LogEvent(source, eventType, messageFmt); } } long getTimestamp() { long timestamp = 0; var timestamper = Context.Timestamper; if (timestamper != null) timestamp = timestamper.Timestamp(); return timestamp; } public void LogEvent(string source, int eventType, long durationNs, string message) { if (Context.HasListeners) { long timestamp = getTimestamp(); var evt = new Event(durationNs, eventType, message, source, timestamp); logEvent(evt); } } public void LogEvent(string source, int eventType, long durationNs, string message, params object[] args) { if (message == null) throw new ArgumentNullException(nameof(message)); if (args == null) throw new ArgumentNullException(nameof(args)); if (source == null) throw new ArgumentNullException(nameof(source)); if (Context.HasListeners) { var messageFmt = string.Format(message, args); LogEvent(source, eventType, durationNs, messageFmt); } } } internal class Log : LogInjector, ILog { private readonly string _source; public Log(LogContext context, string source) : base(context) { _source = source; } public void LogEvent(int eventType, string message) { LogEvent(_source, eventType, message); } public void LogEvent(int eventType, string message, params object[] args) { LogEvent(_source, eventType, message, args); } public void LogEvent(int eventType, long durationNs, string message) { LogEvent(_source, eventType, durationNs, message); } public void LogEvent(int eventType, long durationNs, string message, params object[] args) { LogEvent(_source, eventType, durationNs, message, args); } string ILog.Source => _source; } public long GetProcessedMessages() => processedMessages; } }