diff --git a/EventLook/Model/DataService.cs b/EventLook/Model/DataService.cs index 349a4ba..0ff8e2b 100644 --- a/EventLook/Model/DataService.cs +++ b/EventLook/Model/DataService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Eventing.Reader; +using System.Diagnostics.Tracing; using System.Linq; using System.Text; using System.Threading; @@ -11,28 +12,30 @@ namespace EventLook.Model; public interface IDataService { - Task ReadEvents(LogSource eventSource, DateTime fromTime, DateTime toTime, bool readFromNew, IProgress progress); + Task ReadEvents(LogSource logSource, DateTime fromTime, DateTime toTime, bool readFromNew, IProgress progress); void Cancel(); /// /// Subscribes to events in an event log channel. The caller will be reported whenever a new event comes in. /// - /// + /// /// /// True if success. - bool SubscribeEvents(LogSource eventSource, IProgress progress); + bool SubscribeEvents(LogSource logSource, IProgress progress); void UnsubscribeEvents(); } public class DataService : IDataService { private CancellationTokenSource cts; - public async Task ReadEvents(LogSource eventSource, DateTime fromTime, DateTime toTime, bool readFromNew, IProgress progress) + public async Task ReadEvents(LogSource logSource, DateTime fromTime, DateTime toTime, bool readFromNew, IProgress progress) { using (cts = new CancellationTokenSource()) { // Event records to be sent to the ViewModel var eventRecords = new List(); + EventLogReader reader = null; string errMsg = ""; int count = 0; + int totalCount = 0; bool isFirst = true; try { @@ -40,13 +43,18 @@ public async Task ReadEvents(LogSource eventSource, DateTime fromTime, Date fromTime.ToUniversalTime().ToString("o"), toTime.ToUniversalTime().ToString("o")); - var elQuery = new EventLogQuery(eventSource.Path, eventSource.PathType, sQuery) + var elQuery = new EventLogQuery(logSource.Path, logSource.PathType, sQuery) { ReverseDirection = readFromNew }; - var reader = new EventLogReader(elQuery); - var eventRecord = reader.ReadEvent(); Debug.WriteLine("Begin Reading"); + + // Asynchronously get the record count info. + // The count is valid only when "All time" is selected for a Event Log on the local machine. + _ = Task.Run(() => totalCount = GetRecordCount(logSource)); + + reader = new EventLogReader(elQuery); + var eventRecord = reader.ReadEvent(); await Task.Run(() => { for (; eventRecord != null; eventRecord = reader.ReadEvent()) @@ -57,7 +65,7 @@ await Task.Run(() => ++count; if (count % 100 == 0) { - var info = new ProgressInfo(eventRecords.ConvertAll(e => new EventItem(e)), isComplete: false, isFirst); + var info = new ProgressInfo(eventRecords.ConvertAll(e => new EventItem(e)), isComplete: false, isFirst, totalCount); cts.Token.ThrowIfCancellationRequested(); progress.Report(info); isFirst = false; @@ -84,8 +92,9 @@ await Task.Run(() => } finally { - var info_comp = new ProgressInfo(eventRecords.ConvertAll(e => new EventItem(e)), isComplete: true, isFirst, errMsg); + var info_comp = new ProgressInfo(eventRecords.ConvertAll(e => new EventItem(e)), isComplete: true, isFirst, totalCount, errMsg); progress.Report(info_comp); + reader?.Dispose(); Debug.WriteLine("End Reading"); } return count; @@ -139,7 +148,7 @@ private void EventRecordWrittenHandler(object sender, EventRecordWrittenEventArg } catch (Exception ex) { - progress?.Report(new ProgressInfo(new List(), isComplete: true, isFirst: true, ex.Message)); + progress?.Report(new ProgressInfo(new List(), isComplete: true, isFirst: true, totalEventCount: 0, ex.Message)); } } @@ -198,4 +207,26 @@ public static bool IsValidEventLog(string path, PathType type) } } } + + /// + /// Gets the total number of event records in a channel. + /// This refers to the RecordCount property in EventLogInformation instead of iterating ReadEvent() to count. + /// So the count does not necessarily match the number of records to be received by the query. + /// + private static int GetRecordCount(LogSource source) + { + if (source.PathType == PathType.LogName) + { + try + { + using (var elSession = new EventLogSession()) + { + EventLogInformation info = elSession.GetLogInformation(source.Path, PathType.LogName); + return Convert.ToInt32(info.RecordCount ?? 0); // Int should be large enough. + } + } + catch (Exception) { } + } + return 0; + } } diff --git a/EventLook/Model/Progress.cs b/EventLook/Model/Progress.cs index d495aa9..d5c8414 100644 --- a/EventLook/Model/Progress.cs +++ b/EventLook/Model/Progress.cs @@ -6,19 +6,17 @@ namespace EventLook.Model; -public class ProgressInfo +public class ProgressInfo(IReadOnlyList eventItems, bool isComplete, bool isFirst, int totalEventCount = 0, string message = "") { - public ProgressInfo(IReadOnlyList eventItems, bool isComplete, bool isFirst, string message = "") - { - LoadedEvents = eventItems; - IsComplete = isComplete; - IsFirst = isFirst; - Message = message; - } + public IReadOnlyList LoadedEvents { get; } = eventItems; - public IReadOnlyList LoadedEvents { get; } - - public bool IsComplete { get; } - public bool IsFirst { get; } - public string Message { get; set; } + public bool IsComplete { get; } = isComplete; + public bool IsFirst { get; } = isFirst; + /// + /// Record count information for the local Event Log. + /// Can be 0 if the count is not determined yet or not applicable. + /// For example, when auto refresh is on, or for a .evtx file, it's always 0. + /// + public int RecordCountInfo { get; } = totalEventCount; + public string Message { get; } = message; } diff --git a/EventLook/View/Converter.cs b/EventLook/View/Converter.cs index 264763c..e1522fb 100644 --- a/EventLook/View/Converter.cs +++ b/EventLook/View/Converter.cs @@ -176,18 +176,20 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu /// /// Generates the status text based on the various indicators. -/// 0: IsUpdating, 1: IsAutoRefreshing, 2: IsAppend, 3: LoadedEventCount, 4: AppendCount, 5: LastElapsedTime, 6: ErrorMessage +/// 0: IsUpdating, 1: IsAutoRefreshing, 2: IsAppend, 3: LoadedEventCount, 4: TotalEventCount, 5: AppendCount, 6: LastElapsedTime, 7: ErrorMessage /// public class StatusTextConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values[0] is bool isUpdating && values[1] is bool isAutoRefreshing && values[2] is bool isAppend - && values[3] is int loadedEventCount && values[4] is int appendCount - && values[5] is TimeSpan lastEpasedTime && values[6] is string errorMessage) + && values[3] is int loadedEventCount && values[4] is int totalEventCount && values[5] is int appendCount + && values[6] is TimeSpan lastEpasedTime && values[7] is string errorMessage) { if (isUpdating) - return $"Loading {loadedEventCount} events... {errorMessage}"; + return totalEventCount > 0 + ? $"Loading {loadedEventCount}/{totalEventCount} events... {errorMessage}" + : $"Loading {loadedEventCount} events... {errorMessage}"; else if (isAutoRefreshing) return $"{loadedEventCount} events loaded. Waiting for new events... {errorMessage}"; else diff --git a/EventLook/View/MainWindow.xaml b/EventLook/View/MainWindow.xaml index e23d286..c8fdadc 100644 --- a/EventLook/View/MainWindow.xaml +++ b/EventLook/View/MainWindow.xaml @@ -75,6 +75,7 @@ + diff --git a/EventLook/ViewModel/MainViewModel.cs b/EventLook/ViewModel/MainViewModel.cs index e111802..c0894ec 100644 --- a/EventLook/ViewModel/MainViewModel.cs +++ b/EventLook/ViewModel/MainViewModel.cs @@ -149,6 +149,9 @@ public bool IsAutoRefreshing private int loadedEventCount = 0; public int LoadedEventCount { get => loadedEventCount; set => SetProperty(ref loadedEventCount, value); } + private int totalEventCount = 0; + public int TotalEventCount { get => totalEventCount; set => SetProperty(ref totalEventCount, value); } + private bool isAppend = false; public bool IsAppend { get => isAppend; set => SetProperty(ref isAppend, value); } @@ -408,6 +411,7 @@ private async Task Update(Task task) { ongoingTask = task; stopwatch.Restart(); + TotalEventCount = 0; if (!IsAppend) LoadedEventCount = 0; IsUpdating = true; @@ -442,6 +446,8 @@ private void ProgressCallback(ProgressInfo progressInfo) } LoadedEventCount = Events.Count; + // Disregard unless the range is "All time". + TotalEventCount = (SelectedRange.DaysFromNow == 0 && !SelectedRange.IsCustom) ? progressInfo.RecordCountInfo : 0; ErrorMessage = progressInfo.Message; if (progressInfo.IsComplete) @@ -473,6 +479,7 @@ private void TurnOnAutoRefresh() if (!IsAppend) Refresh(reset: false, append: true); DataService.SubscribeEvents(SelectedLogSource, progressAutoRefresh); + TotalEventCount = 0; IsAutoRefreshing = true; } private void TurnOffAutoRefresh()