Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
olivier-spinelli committed Dec 15, 2017
2 parents 9a598c9 + 7af8863 commit 4c044d3
Show file tree
Hide file tree
Showing 18 changed files with 515 additions and 67 deletions.
2 changes: 1 addition & 1 deletion CK-Monitoring.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.8
VisualStudioVersion = 15.0.27004.2005
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C51E8D11-FC45-4D5B-85DC-68A7EEB83601}"
ProjectSection(SolutionItems) = preProject
Expand Down
2 changes: 1 addition & 1 deletion CK.Monitoring/CK.Monitoring.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CK.ActivityMonitor" Version="9.0.0" />
<PackageReference Include="CK.ActivityMonitor" Version="9.0.1" />
</ItemGroup>


Expand Down
17 changes: 17 additions & 0 deletions CK.Monitoring/InterProcess/LogReceiverEndStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace CK.Monitoring.InterProcess
{
/// <summary>
/// Defines the final status of a <see cref=""/>
/// </summary>
public enum LogReceiverEndStatus
{
None,
Normal,
MissingEndMarker,
Error
}
}
139 changes: 139 additions & 0 deletions CK.Monitoring/InterProcess/SimplePipeLogReceiver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using CK.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;

namespace CK.Monitoring.InterProcess
{
/// <summary>
/// This receiver is the server of a <see cref="SimplePipeSenderActivityMonitorClient"/>.
/// It creates a thread that receives the log entries from the external client and inject
/// them in a local target monitor.
/// <para>
/// This receiver and its associated sender client cover a simple scenario where a process
/// launch another simple process that uses one and only one monitor. The logs emitted by the
/// child process appear to be from the parent process, incorporated in the activity of the
/// parent process local monitor.
/// </para>
/// </summary>
public class SimpleLogPipeReceiver : IDisposable
{
readonly AnonymousPipeServerStream _server;
readonly CKBinaryReader _reader;
readonly IActivityMonitor _monitor;
readonly Thread _thread;
readonly bool _interProcess;
LogReceiverEndStatus _endFlag;

SimpleLogPipeReceiver( IActivityMonitor m, bool interProcess )
{
_interProcess = interProcess;
var inherit = interProcess ? HandleInheritability.Inheritable : HandleInheritability.None;
_server = new AnonymousPipeServerStream( PipeDirection.In, inherit );
_reader = new CKBinaryReader( _server );
_monitor = m;
PipeName = _server.GetClientHandleAsString();
_thread = new Thread( Run );
_thread.IsBackground = true;
_thread.Start();
}

/// <summary>
/// Gets the pipe handler name that must be transmitted to the <see cref="SimplePipeSenderActivityMonitorClient"/>.
/// </summary>
public string PipeName { get; }

/// <summary>
/// Waits for the termination of the other side.
/// If it is known that the client has failed (typically because external process ended with a non zero
/// return code), <paramref name="otherFailed"/> should be true: this receiver will only wait for 500 ms
/// before returning, avoiding to wait for the internal thread termination.
/// When <paramref name="otherFailed"/> is false, this method blocks until the client sends its goodbye
/// message or the pipe is broken.
/// </summary>
/// <param name="otherFailed">True when you already know that the sender has failed.</param>
/// <returns>The final status.</returns>
public LogReceiverEndStatus WaitEnd( bool otherFailed )
{
if( otherFailed )
{
if( !_thread.Join( 500 ) ) _endFlag = LogReceiverEndStatus.Error;
}
else _thread.Join();
return _endFlag;
}

/// <summary>
/// Waits for the termination of the internal thread and closes the pipe.
/// Even if this can be called immediately, you should first call <see cref="WaitEnd(bool)"/>
/// before calling Dispose.
/// </summary>
public void Dispose()
{
if( _endFlag == LogReceiverEndStatus.None ) _thread.Join();
_reader.Dispose();
_server.Dispose();
}

void Run()
{
try
{
int streamVersion = _reader.ReadInt32();
if( _interProcess ) _server.DisposeLocalCopyOfClientHandle();
for(; ; )
{
var e = LogEntry.Read( _reader, streamVersion, out bool badEndOfStream );
if( e == null || badEndOfStream )
{
_endFlag = badEndOfStream ? LogReceiverEndStatus.MissingEndMarker : LogReceiverEndStatus.Normal;
break;
}
switch( e.LogType )
{
case LogEntryType.Line:
if( _monitor.ShouldLogLine( e.LogLevel, e.FileName, e.LineNumber ) )
{
_monitor.UnfilteredLog( e.Tags, e.LogLevel | LogLevel.IsFiltered, e.Text, e.LogTime, CKException.CreateFrom( e.Exception ), e.FileName, e.LineNumber );
}
break;
case LogEntryType.OpenGroup:
_monitor.UnfilteredOpenGroup( _monitor.ShouldLogGroup( e.LogLevel, e.FileName, e.LineNumber )
? new ActivityMonitorGroupData( e.LogLevel | LogLevel.IsFiltered, e.Tags, e.Text, e.LogTime, CKException.CreateFrom( e.Exception ), null, e.FileName, e.LineNumber )
: null );
break;
case LogEntryType.CloseGroup:
_monitor.CloseGroup( e.LogTime, e.Conclusions );
break;
}
}
}
catch( Exception ex )
{
_endFlag = LogReceiverEndStatus.Error;
ActivityMonitor.CriticalErrorCollector.Add( ex, $"While {nameof( SimpleLogPipeReceiver)}.Run." );
}
}

/// <summary>
/// Starts a receiver.
/// <para>
/// Its <see cref="PipeName"/> must be given to the <see cref="SimplePipeSenderActivityMonitorClient"/>
/// (typically with a /logpipe: argument for the launched process) and <see cref="WaitEnd(bool)"/> should be
/// called before disposing it.
/// </para>
/// <para>
/// Once the child process has been started, no more logs should be emitted in the local monitor: the internal thread
/// will receive the logs from the external client and relay them into the local monitor.
/// </para>
/// </summary>
/// <param name="localMonitor">The local monitor to which all collected logs will be injected.</param>
/// <param name="interProcess">True when the client will be created in another process. False for an intra-process client (but why would you need this?).</param>
/// <returns>A started receiver, ready to inject external logs into the local monitor.</returns>
public static SimpleLogPipeReceiver Start( IActivityMonitor localMonitor, bool interProcess = true ) => new SimpleLogPipeReceiver( localMonitor, interProcess );

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using CK.Core;
using CK.Monitoring;
using System;
using System.Collections.Generic;
using System.IO.Pipes;
using System.Text;

namespace CK.Monitoring.InterProcess
{
/// <summary>
/// Simple activity monitor client that uses a <see cref="AnonymousPipeClientStream"/> to sends log
/// entries to a <see cref="SimpleLogPipeReceiver"/>.
/// </summary>
public class SimplePipeSenderActivityMonitorClient : IActivityMonitorClient, IDisposable
{
readonly CKBinaryWriter _writer;
readonly AnonymousPipeClientStream _client;
bool _disposed;

/// <summary>
/// Initializes a new <see cref="SimplePipeSenderActivityMonitorClient"/>.
/// </summary>
/// <param name="pipeHandlerName">The name of the server pipe.</param>
public SimplePipeSenderActivityMonitorClient( string pipeHandlerName )
{
_client = new AnonymousPipeClientStream( PipeDirection.Out, pipeHandlerName );
_writer = new CKBinaryWriter( _client );
_writer.Write( LogReader.CurrentStreamVersion );
}

/// <summary>
/// Sends a goodbye message (a zero byte) and closes this side of the pipe.
/// </summary>
public void Dispose()
{
if( !_disposed )
{
_disposed = true;
_client.WriteByte( 0 );
_writer.Dispose();
_client.Dispose();
}
}

void IActivityMonitorClient.OnAutoTagsChanged( CKTrait newTrait )
{
}

void IActivityMonitorClient.OnGroupClosed( IActivityLogGroup group, IReadOnlyList<ActivityLogGroupConclusion> conclusions )
{
LogEntry.WriteCloseGroup( _writer, group.GroupLevel, group.CloseLogTime, conclusions );
}

void IActivityMonitorClient.OnGroupClosing( IActivityLogGroup group, ref List<ActivityLogGroupConclusion> conclusions )
{
}

void IActivityMonitorClient.OnOpenGroup( IActivityLogGroup group )
{
LogEntry.WriteLog( _writer, true, group.GroupLevel, group.LogTime, group.GroupText, group.GroupTags, group.ExceptionData, group.FileName, group.LineNumber );
}

void IActivityMonitorClient.OnTopicChanged( string newTopic, string fileName, int lineNumber )
{
}

void IActivityMonitorClient.OnUnfilteredLog( ActivityMonitorLogData data )
{
LogEntry.WriteLog( _writer, false, data.Level, data.LogTime, data.Text, data.Tags, data.ExceptionData, data.FileName, data.LineNumber );
}
}
}
4 changes: 2 additions & 2 deletions CK.Monitoring/Persistence/LogEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ static void DoWriteCloseGroup( CKBinaryWriter w, StreamLogType t, LogLevel level
/// If the first read byte is 0, read stops and null is returned.
/// The 0 byte is the "end marker" that <see cref="CKMonWriterClient.Close()"/> write, but this
/// method can read non zero-terminated streams (it catches an EndOfStreamException when reading the first byte and handles it silently).
/// This method can throw any type of exception (<see cref="System.IO.EndOfStreamException"/> or <see cref="InvalidDataException"/> for instance) that
/// must be handled by the caller.
/// This method can throw any type of exception except <see cref="System.IO.EndOfStreamException"/>
/// (like <see cref="InvalidDataException"/> for instance) that must be handled by the caller.
/// </summary>
/// <param name="r">The binary reader.</param>
/// <param name="streamVersion">The version of the stream.</param>
Expand Down
5 changes: 3 additions & 2 deletions CK.Monitoring/Persistence/LogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,12 @@ public IMulticastLogEntry CurrentMulticast
/// <summary>
/// Gets whether the end of file has been reached and the file is missing the final 0 byte marker.
/// </summary>
public bool BadEndOfFileMarker => _badEndOfFille;
public bool BadEndOfFileMarker => _badEndOfFille;

/// <summary>
/// Current <see cref="IMulticastLogEntry"/> with its associated position in the stream.
/// The current entry must be a multi-cast one and, as usual, <see cref="MoveNext"/> must be called before getting the first entry.
/// The current entry must be a multi-cast one and, as usual, <see cref="MoveNext"/> must be
/// called before getting the first entry.
/// </summary>
public MulticastLogEntryWithOffset CurrentMulticastWithOffset
{
Expand Down
34 changes: 17 additions & 17 deletions CK.Monitoring/Persistence/MultiLogReader.ActivityMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public sealed class ActivityMap
readonly IReadOnlyCollection<RawLogFile> _allFiles;
readonly IReadOnlyCollection<RawLogFile> _validFiles;
readonly IReadOnlyList<Monitor> _monitorList;
readonly Dictionary<Guid,Monitor> _monitors;
readonly Dictionary<Guid, Monitor> _monitors;
readonly DateTime _firstEntryDate;
readonly DateTime _lastEntryDate;

Expand All @@ -36,27 +36,27 @@ internal ActivityMap( MultiLogReader reader )
/// <summary>
/// Gets the very first entry time (among all <see cref="Monitors"/>).
/// </summary>
public DateTime FirstEntryDate => _firstEntryDate;
public DateTime FirstEntryDate => _firstEntryDate;

/// <summary>
/// Gets the very last entry time (among all <see cref="Monitors"/>).
/// </summary>
public DateTime LastEntryDate => _lastEntryDate;
public DateTime LastEntryDate => _lastEntryDate;

/// <summary>
/// Gets the valid files (see <see cref="RawLogFile.IsValidFile"/>).
/// </summary>
public IReadOnlyCollection<RawLogFile> ValidFiles => _validFiles;
public IReadOnlyCollection<RawLogFile> ValidFiles => _validFiles;

/// <summary>
/// Gets all files (even the ones for which <see cref="RawLogFile.IsValidFile"/> is false).
/// </summary>
public IReadOnlyCollection<RawLogFile> AllFiles => _allFiles;
public IReadOnlyCollection<RawLogFile> AllFiles => _allFiles;

/// <summary>
/// Gets all the monitors that this ActivityMap contains ordered by their <see cref="Monitor.FirstEntryTime"/>.
/// </summary>
public IReadOnlyList<Monitor> Monitors => _monitorList;
public IReadOnlyList<Monitor> Monitors => _monitorList;

/// <summary>
/// Finds a <see cref="Monitor"/> by its identifier.
Expand All @@ -77,7 +77,7 @@ public class Monitor
readonly int _firstDepth;
readonly DateTimeStamp _lastEntryTime;
readonly int _lastDepth;
readonly IReadOnlyList<KeyValuePair<CKTrait,int>> _tags;
readonly IReadOnlyList<KeyValuePair<CKTrait, int>> _tags;

internal Monitor( LiveIndexedMonitor m )
{
Expand All @@ -93,17 +93,17 @@ internal Monitor( LiveIndexedMonitor m )
/// <summary>
/// Gets the monitor's identifier.
/// </summary>
public Guid MonitorId => _monitorId;
public Guid MonitorId => _monitorId;

/// <summary>
/// Gets the different files where entries from this monitor appear.
/// </summary>
public IReadOnlyList<RawLogFileMonitorOccurence> Files => _files;
public IReadOnlyList<RawLogFileMonitorOccurence> Files => _files;

/// <summary>
/// Gets the very first known entry time for this monitor.
/// </summary>
public DateTimeStamp FirstEntryTime => _firstEntryTime;
public DateTimeStamp FirstEntryTime => _firstEntryTime;

/// <summary>
/// Gets the very first known depth for this monitor.
Expand All @@ -118,7 +118,7 @@ internal Monitor( LiveIndexedMonitor m )
/// <summary>
/// Gets the very last known depth for this monitor.
/// </summary>
public int LastDepth => _lastDepth;
public int LastDepth => _lastDepth;

/// <summary>
/// Gets the weighted occurrences of each tags that have been logged in this monitor.
Expand Down Expand Up @@ -422,7 +422,7 @@ internal LivePage( int initialGroupDepth, ParentedLogEntry[] entries, MultiFileR
/// <summary>
/// Gets the log entries of the current page.
/// </summary>
public IReadOnlyList<ParentedLogEntry> Entries => _entries;
public IReadOnlyList<ParentedLogEntry> Entries => _entries;

/// <summary>
/// Gets the page length.
Expand Down Expand Up @@ -490,13 +490,13 @@ public LivePage ReadFirstPage( DateTimeStamp firstLogTime, int pageLength )
/// </summary>
/// <param name="pageLength">Page length.</param>
/// <returns>All log entries in order for this Monitor.</returns>
public IEnumerable<ParentedLogEntry> ReadAllEntries(int pageLength = 1024)
public IEnumerable<ParentedLogEntry> ReadAllEntries( int pageLength = 1024 )
{
using (var p = ReadFirstPage(pageLength))
using( var p = ReadFirstPage( pageLength ) )
{
foreach (var e in p.Entries) yield return e;
while (p.ForwardPage() > 0)
foreach (var e in p.Entries) yield return e;
foreach( var e in p.Entries ) yield return e;
while( p.ForwardPage() > 0 )
foreach( var e in p.Entries ) yield return e;
}
}

Expand Down
Loading

0 comments on commit 4c044d3

Please sign in to comment.