// 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;
}
}