From 09ebd4495511251751bd1e9373ce46f4aa099297 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Mon, 15 Jan 2024 17:39:27 -0500 Subject: [PATCH 1/6] Reduce x-proc buffer size from 4096 to 512 to increase performance --- src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp b/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp index c5bce63b6..8496bfd7f 100644 --- a/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp +++ b/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp @@ -21,7 +21,7 @@ CMidi2MidiSrv::Initialize( TraceLoggingPointer(this, "this") ); - MIDISRV_CLIENTCREATION_PARAMS creationParams{ 0 }; + MIDISRV_CLIENTCREATION_PARAMS creationParams{ }; PMIDISRV_CLIENT client{ nullptr }; wil::unique_rpc_binding bindingHandle; @@ -48,8 +48,13 @@ CMidi2MidiSrv::Initialize( creationParams.Flow = Flow; creationParams.DataFormat = CreationParams->DataFormat; + // Todo: client side buffering requests to come from some service setting? - creationParams.BufferSize = PAGE_SIZE; + // - See https://github.com/microsoft/MIDI/issues/219 for details + + //creationParams.BufferSize = PAGE_SIZE; + creationParams.BufferSize = 512; // for debugging DON'T LEAVE THIS IN + RETURN_IF_FAILED(GetMidiSrvBindingHandle(&bindingHandle)); From 1405e72a0aa39eea6454f0cc6d7ed6ee25fd98b9 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Mon, 15 Jan 2024 17:39:55 -0500 Subject: [PATCH 2/6] Add SAL to KSMidiDevice::ConfigureLoopedBuffer to get rid of warning --- src/api/Libs/MidiKs/MidiKs.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/Libs/MidiKs/MidiKs.cpp b/src/api/Libs/MidiKs/MidiKs.cpp index ff39318d0..d1f2932f9 100644 --- a/src/api/Libs/MidiKs/MidiKs.cpp +++ b/src/api/Libs/MidiKs/MidiKs.cpp @@ -156,6 +156,7 @@ KSMidiDevice::PinSetState( return S_OK; } +_Use_decl_annotations_ HRESULT KSMidiDevice::ConfigureLoopedBuffer(ULONG& BufferSize ) From 7406a868b1864fce37f03bfedc71a8b0e6e1931b Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Mon, 15 Jan 2024 17:41:26 -0500 Subject: [PATCH 3/6] Update console monitor to let us know if any messages arrive out of order Also further pared down the display if you don't choose --verbose. Optimizing for performance, here. The message type now only shows up with the --verbose switch. Without verbose, we're focusing on raw data. --- build/staging/version/BundleInfo.wxi | 2 +- .../Endpoint/EndpointMonitorCommand.cs | 27 ++++++- .../Midi/CustomTable/MidiMessageTable.cs | 81 ++++++++++++++----- 3 files changed, 85 insertions(+), 25 deletions(-) diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi index c89377f36..df71ef909 100644 --- a/build/staging/version/BundleInfo.wxi +++ b/build/staging/version/BundleInfo.wxi @@ -1,4 +1,4 @@ - + diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs index 02e449368..fc13b5464 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs @@ -129,7 +129,7 @@ private void MonitorEndpointConnectionStatusInTheBackground(string endpointId) while (_continueWatchingDevice) { - Thread.Sleep(125); + Thread.Sleep(100); } }); @@ -208,12 +208,16 @@ public override int Execute(CommandContext context, Settings settings) UInt32 index = 0; + MonitorEndpointConnectionStatusInTheBackground(endpointId); bool continueWaiting = true; + UInt64 outOfOrderMessageCount = 0; var messageListener = new Thread(() => { + UInt64 lastMessageTimestamp = 0; + connection.MessageReceived += (s, e) => { // helps prevent any race conditions with main loop and its output @@ -229,6 +233,14 @@ public override int Execute(CommandContext context, Settings settings) MessageTimestamp = e.Timestamp }; + if (e.Timestamp < lastMessageTimestamp) + { + outOfOrderMessageCount++; + } + + lastMessageTimestamp = e.Timestamp; + + receivedMessage.NumWords = e.FillWords(out receivedMessage.Word0, out receivedMessage.Word1, out receivedMessage.Word2, out receivedMessage.Word3); m_receivedMessagesQueue.Enqueue(receivedMessage); @@ -249,6 +261,8 @@ public override int Execute(CommandContext context, Settings settings) messageListener.Start(); + + // open the connection if (connection.Open()) { @@ -280,7 +294,7 @@ public override int Execute(CommandContext context, Settings settings) { int iter = 0; - while (m_receivedMessagesQueue.Count > 0 && iter < 50) + while (m_receivedMessagesQueue.Count > 0 && iter < 10) { iter++; // we need to take a break if we're being spammed, so this tracks iterations @@ -352,6 +366,15 @@ public override int Execute(CommandContext context, Settings settings) session.DisconnectEndpointConnection(connection.ConnectionId); } + if (outOfOrderMessageCount > 0) + { + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(outOfOrderMessageCount.ToString() + " messages received out of sent timestamp order.")); + } + else + { + AnsiConsole.MarkupLine("No messages received out of expected timestamp order."); + } + if (captureWriter != null) { AnsiConsole.MarkupLine("Messages written to " + AnsiMarkupFormatter.FormatFileName(fileName)); diff --git a/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs b/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs index 65a820cd1..333f29164 100644 --- a/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs +++ b/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs @@ -2,13 +2,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Windows.Devices.Midi2; namespace Microsoft.Devices.Midi2.ConsoleApp { - internal struct MidiMessageTableColumn + internal class MidiMessageTableColumn { public int ParameterIndex { get; set; } public string HeaderText { get; set; } @@ -98,16 +99,28 @@ private void BuildStringFormats() const int timestampOffsetValueColumnWidth = 9; + private bool m_verbose = false; + private int m_offsetColumnNumber = 2; + private int m_deltaColumnNumber = 5; + + private string m_offsetValueDefaultColor = "darkseagreen"; + private string m_offsetValueErrorColor = "red"; + + private string m_deltaValueDefaultColor = "lightskyblue3_1"; + private string m_deltaValueErrorColor = "red"; + public MidiMessageTable(bool verbose) { + m_verbose = verbose; + // todo: localize strings Columns.Add(new MidiMessageTableColumn(0, "Index", 8, true, "grey", "")); Columns.Add(new MidiMessageTableColumn(1, "Message Timestamp", 19, false, "darkseagreen2", "N0")); // timestamp - Columns.Add(new MidiMessageTableColumn(2, "From Last", timestampOffsetValueColumnWidth, false, "darkseagreen", "")); // offset + Columns.Add(new MidiMessageTableColumn(m_offsetColumnNumber, "From Last", timestampOffsetValueColumnWidth, false, m_offsetValueDefaultColor, "")); // offset Columns.Add(new MidiMessageTableColumn(3, "", -2, true, "grey", "")); // offset label if (verbose) Columns.Add(new MidiMessageTableColumn(4, "Received Timestamp", 19, false, "skyblue2", "N0")); // recv timestamp - if (verbose) Columns.Add(new MidiMessageTableColumn(5, "Receive \u0394", timestampOffsetValueColumnWidth, false, "lightskyblue3_1", "")); // delta + if (verbose) Columns.Add(new MidiMessageTableColumn(m_deltaColumnNumber, "Receive \u0394", timestampOffsetValueColumnWidth, false, "lightskyblue3_1", "")); // delta if (verbose) Columns.Add(new MidiMessageTableColumn(6, "", -2, true, "grey", "")); // delta label Columns.Add(new MidiMessageTableColumn(7, "Words", 8, false, "deepskyblue1", "")); Columns.Add(new MidiMessageTableColumn(8, "", 8, true, "deepskyblue2", "")); @@ -115,7 +128,7 @@ public MidiMessageTable(bool verbose) Columns.Add(new MidiMessageTableColumn(10, "", 8, true, "deepskyblue4", "")); if (verbose) Columns.Add(new MidiMessageTableColumn(11, "Gr", 2, false, "indianred", "")); if (verbose) Columns.Add(new MidiMessageTableColumn(12, "Ch", 2, true, "mediumorchid3", "")); - Columns.Add(new MidiMessageTableColumn(13, "Message Type", -35, false,"steelblue1_1", "")); + if (verbose) Columns.Add(new MidiMessageTableColumn(13, "Message Type", -35, false,"steelblue1_1", "")); BuildStringFormats(); } @@ -130,7 +143,9 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP { string data = string.Empty; - string detailedMessageType = MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(message.Word0); + string detailedMessageType = string.Empty; + + if (m_verbose) detailedMessageType = MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(message.Word0); string word0 = string.Empty; string word1 = string.Empty; @@ -165,36 +180,46 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP double deltaValue = 0; string deltaUnitLabel = string.Empty; - double deltaSecheduledTimestampMicroseconds = 0.0; - - if (message.ReceivedTimestamp >= message.MessageTimestamp) - { - deltaSecheduledTimestampMicroseconds = MidiClock.ConvertTimestampToMicroseconds(message.ReceivedTimestamp - message.MessageTimestamp); - } - else + if (m_verbose) { - // we received the message early, so the offset is negative - deltaSecheduledTimestampMicroseconds = -1 * MidiClock.ConvertTimestampToMicroseconds(message.MessageTimestamp - message.ReceivedTimestamp); - } + double deltaSecheduledTimestampMicroseconds = 0.0; + if (message.ReceivedTimestamp >= message.MessageTimestamp) + { + deltaSecheduledTimestampMicroseconds = MidiClock.ConvertTimestampToMicroseconds(message.ReceivedTimestamp - message.MessageTimestamp); + } + else + { + // we received the message early, so the offset is negative + deltaSecheduledTimestampMicroseconds = -1 * MidiClock.ConvertTimestampToMicroseconds(message.MessageTimestamp - message.ReceivedTimestamp); + } - AnsiConsoleOutput.ConvertToFriendlyTimeUnit(deltaSecheduledTimestampMicroseconds, out deltaValue, out deltaUnitLabel); + AnsiConsoleOutput.ConvertToFriendlyTimeUnit(deltaSecheduledTimestampMicroseconds, out deltaValue, out deltaUnitLabel); + } string groupText = string.Empty; - var messageType = MidiMessageUtility.GetMessageTypeFromMessageFirstWord(message.Word0); - - if (MidiMessageUtility.MessageTypeHasGroupField(messageType)) + if (m_verbose) { - groupText = MidiMessageUtility.GetGroupFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2); + var messageType = MidiMessageUtility.GetMessageTypeFromMessageFirstWord(message.Word0); + + if (MidiMessageUtility.MessageTypeHasGroupField(messageType)) + { + groupText = MidiMessageUtility.GetGroupFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2); + } } string channelText = string.Empty; - if (MidiMessageUtility.MessageTypeHasChannelField(messageType)) + if (m_verbose) { - channelText = MidiMessageUtility.GetChannelFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2); + var messageType = MidiMessageUtility.GetMessageTypeFromMessageFirstWord(message.Word0); + + if (MidiMessageUtility.MessageTypeHasChannelField(messageType)) + { + channelText = MidiMessageUtility.GetChannelFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2); + } } // some cleanup @@ -218,11 +243,23 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP if (deltaValueText.Length > timestampOffsetValueColumnWidth) { deltaValueText = "######"; + deltaUnitLabel = ""; + Columns[m_deltaColumnNumber].DataColor = m_deltaValueErrorColor; + } + else + { + Columns[m_deltaColumnNumber].DataColor = m_deltaValueDefaultColor; } if (offsetValueText.Length > timestampOffsetValueColumnWidth) { offsetValueText = "######"; + offsetUnitLabel = ""; + Columns[m_offsetColumnNumber].DataColor = m_offsetValueErrorColor; + } + else + { + Columns[m_offsetColumnNumber].DataColor = m_offsetValueDefaultColor; } From 227baf6066f148291f8f6b33a2538d10725f7085 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Mon, 15 Jan 2024 19:44:13 -0500 Subject: [PATCH 4/6] Refactoring monitor code in midi console Significant performance boost in console monitoring, especially when writing to a file. console input (UI thread), message Listening, display output, and file writing are all separate threads now. Next step will be to evaluate notification events (like an autoresetevent) rather than just spinning and using extra CPU. --- build/staging/version/BundleInfo.wxi | 2 +- .../Endpoint/EndpointMonitorCommand.cs | 266 ++++++++++++------ 2 files changed, 176 insertions(+), 92 deletions(-) diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi index df71ef909..8bdde3584 100644 --- a/build/staging/version/BundleInfo.wxi +++ b/build/staging/version/BundleInfo.wxi @@ -1,4 +1,4 @@ - + diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs index fc13b5464..c986820cf 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs @@ -35,6 +35,12 @@ internal class EndpointMonitorCommand : Command // we have this struct so we can separate the relatively fast received processing // and its calculations from the comparatively slow displays processing private Queue m_receivedMessagesQueue = new Queue(); + private Queue m_displayMessageQueue = new Queue(); + private Queue m_fileWriterMessagesQueue = new Queue(); + + + // private static AutoResetEvent m_displayMessageQueueNewMessage = new AutoResetEvent(true); + // private static AutoResetEvent m_fileMessageQueueNewMessage = new AutoResetEvent(false); @@ -137,6 +143,11 @@ private void MonitorEndpointConnectionStatusInTheBackground(string endpointId) deviceWatcherThread.Start(); } + + + + + public override int Execute(CommandContext context, Settings settings) { MidiMessageTable displayTable = new MidiMessageTable(settings.Verbose); @@ -203,9 +214,6 @@ public override int Execute(CommandContext context, Settings settings) UInt64 startTimestamp = 0; UInt64 lastReceivedTimestamp = 0; - - //MidiMessageStruct msg; - UInt32 index = 0; @@ -214,58 +222,150 @@ public override int Execute(CommandContext context, Settings settings) bool continueWaiting = true; UInt64 outOfOrderMessageCount = 0; - var messageListener = new Thread(() => + + // open the connection + if (connection.Open()) { - UInt64 lastMessageTimestamp = 0; + // Main message listener background thread ----------------------------------------------------- - connection.MessageReceived += (s, e) => + var messageListener = new Thread(() => { - // helps prevent any race conditions with main loop and its output - if (!continueWaiting) return; - - //Console.WriteLine("DEBUG: MessageReceived"); - index++; + UInt64 lastMessageTimestamp = 0; - var receivedMessage = new ReceivedMidiMessage() + connection.MessageReceived += (s, e) => { - Index = index, - ReceivedTimestamp = MidiClock.Now, - MessageTimestamp = e.Timestamp + // helps prevent any race conditions with main loop and its output + if (!continueWaiting) return; + + //Console.WriteLine("DEBUG: MessageReceived"); + index++; + + var receivedMessage = new ReceivedMidiMessage() + { + Index = index, + ReceivedTimestamp = MidiClock.Now, + MessageTimestamp = e.Timestamp + }; + + if (e.Timestamp < lastMessageTimestamp) + { + outOfOrderMessageCount++; + } + + lastMessageTimestamp = e.Timestamp; + + + receivedMessage.NumWords = e.FillWords(out receivedMessage.Word0, out receivedMessage.Word1, out receivedMessage.Word2, out receivedMessage.Word3); + + lock (m_receivedMessagesQueue) + { + m_receivedMessagesQueue.Enqueue(receivedMessage); + } + + if (settings.SingleMessage) + { + continueWaiting = false; + } }; - if (e.Timestamp < lastMessageTimestamp) + // spin wait. We can change this to an event. + while (continueWaiting && !_hasEndpointDisconnected) { - outOfOrderMessageCount++; + Thread.Sleep(0); } - lastMessageTimestamp = e.Timestamp; + }); + messageListener.Start(); - receivedMessage.NumWords = e.FillWords(out receivedMessage.Word0, out receivedMessage.Word1, out receivedMessage.Word2, out receivedMessage.Word3); + // Console display background thread ----------------------------------------------------- + var messageConsoleDisplay = new Thread(() => + { + while (continueWaiting) + { + if (m_displayMessageQueue.Count > 0) + { + ReceivedMidiMessage message; + + lock (m_displayMessageQueue) + { + message = m_displayMessageQueue.Dequeue(); + } - m_receivedMessagesQueue.Enqueue(receivedMessage); + if (startTimestamp == 0) + { + // gets timestamp of first message we receive and uses that so all others are an offset + startTimestamp = message.ReceivedTimestamp; + } - if (settings.SingleMessage) - { - continueWaiting = false; + if (lastReceivedTimestamp == 0) + { + // gets timestamp of first message we receive and uses that so all others are an offset from previous message + lastReceivedTimestamp = message.ReceivedTimestamp; + } + + if (message.Index == 1) + { + displayTable.OutputHeader(); + } + + // calculate offset from the last message received + var offsetMicroseconds = MidiClock.ConvertTimestampToMicroseconds( + message.ReceivedTimestamp - lastReceivedTimestamp); + + // set our last received so we can calculate offsets + lastReceivedTimestamp = message.ReceivedTimestamp; + + + displayTable.OutputRow(message, offsetMicroseconds); + } + + Thread.Sleep(0); } - }; + }); + messageConsoleDisplay.Start(); + - while (continueWaiting && !_hasEndpointDisconnected) + // File writing background thread ----------------------------------------------------- + if (captureWriter != null) { - Thread.Sleep(0); - } + var messageFileWriter = new Thread(() => + { + while (continueWaiting) + { + if (m_fileWriterMessagesQueue.Count > 0) + { + ReceivedMidiMessage message; + + lock (m_fileWriterMessagesQueue) + { + message = m_fileWriterMessagesQueue.Dequeue(); + } - }); - messageListener.Start(); + // write header if first message + if (message.Index == 1) + { + captureWriter.WriteLine("#"); + captureWriter.WriteLine($"# Windows MIDI Services Console Capture {DateTime.Now.ToLongDateString()}"); + captureWriter.WriteLine($"# Endpoint: {endpointId.ToString()}"); + captureWriter.WriteLine($"# Annotation: {settings.AnnotateCaptureFile.ToString()}"); + captureWriter.WriteLine($"# Delimiter: {settings.FieldDelimiter.ToString()}"); + captureWriter.WriteLine("#"); + } + + WriteMessageToFile(settings, captureWriter, message); + } + Thread.Sleep(0); + } + }); + messageFileWriter.Start(); + } - // open the connection - if (connection.Open()) - { + // Main UI loop ----------------------------------------------------- while (continueWaiting) { if (Console.KeyAvailable) @@ -277,86 +377,53 @@ public override int Execute(CommandContext context, Settings settings) continueWaiting = false; AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine(Strings.MonitorEscapePressedMessage); + AnsiConsole.MarkupLine("🛑 " + Strings.MonitorEscapePressedMessage); } } - if (_hasEndpointDisconnected) { continueWaiting = false; AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(Strings.EndpointDisconnected)); } - else if (continueWaiting && m_receivedMessagesQueue.Count > 0) + else if (continueWaiting && m_receivedMessagesQueue.Count > 0) { - lock (m_receivedMessagesQueue) - { - int iter = 0; - - while (m_receivedMessagesQueue.Count > 0 && iter < 10) - { - iter++; // we need to take a break if we're being spammed, so this tracks iterations - - var message = m_receivedMessagesQueue.Dequeue(); - - if (startTimestamp == 0) - { - // gets timestamp of first message we receive and uses that so all others are an offset - startTimestamp = message.ReceivedTimestamp; - } + // we load up the various queues here - if (lastReceivedTimestamp == 0) - { - // gets timestamp of first message we receive and uses that so all others are an offset from previous message - lastReceivedTimestamp = message.ReceivedTimestamp; - } - - // TODO: Maybe re-display the header if it's been a while since last header, and it is off-screen (index delta > some number) - if (message.Index == 1) - { - displayTable.OutputHeader(); + ReceivedMidiMessage message; - - if (captureWriter != null && capturingToFile) - { - captureWriter.WriteLine("#"); - captureWriter.WriteLine($"# Windows MIDI Services Console Capture {DateTime.Now.ToLongDateString()}"); - captureWriter.WriteLine($"# Endpoint: {endpointId.ToString()}"); - captureWriter.WriteLine($"# Annotation: {settings.AnnotateCaptureFile.ToString()}"); - captureWriter.WriteLine($"# Delimiter: {settings.FieldDelimiter.ToString()}"); - captureWriter.WriteLine("#"); - } - - } - - // calculate offset from the last message received - var offsetMicroseconds = MidiClock.ConvertTimestampToMicroseconds(message.ReceivedTimestamp - lastReceivedTimestamp); - - displayTable.OutputRow(message, offsetMicroseconds); - - if (captureWriter != null) - { - WriteMessageToFile(settings, captureWriter, message); - } + // pull from the incoming messages queue + lock (m_receivedMessagesQueue) + { + message = m_receivedMessagesQueue.Dequeue(); + } - // set our last received so we can calculate offsets - lastReceivedTimestamp = message.ReceivedTimestamp; + // add to the display queue + lock (m_displayMessageQueue) + { + m_displayMessageQueue.Enqueue(message); + } + // add to the file writer queue if we're capturing to file + if (captureWriter != null) + { + lock (m_fileWriterMessagesQueue) + { + m_fileWriterMessagesQueue.Enqueue(message); } } } - - // we don't need to update the display more often. A tight loop really ties up the CPU. - // actual message receive timings are based on the worker thread - if (continueWaiting) Thread.Sleep(125); + + if (continueWaiting) Thread.Sleep(10); } } else { AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(Strings.ErrorUnableToOpenEndpoint)); return (int)MidiConsoleReturnCode.ErrorOpeningEndpointConnection; + } _continueWatchingDevice = false; @@ -366,18 +433,35 @@ public override int Execute(CommandContext context, Settings settings) session.DisconnectEndpointConnection(connection.ConnectionId); } + + + // Summary information + if (outOfOrderMessageCount > 0) { - AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(outOfOrderMessageCount.ToString() + " messages received out of sent timestamp order.")); + string message = "❎ " + outOfOrderMessageCount.ToString(); + + if (outOfOrderMessageCount > 1) + { + // multiple messages out of order + message += " messages received out of sent timestamp order."; + } + else + { + // single message out of order + message += " message received out of sent timestamp order."; + } + + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(message)); } else { - AnsiConsole.MarkupLine("No messages received out of expected timestamp order."); + AnsiConsole.MarkupLine("✅ No messages received out of expected timestamp order."); } if (captureWriter != null) { - AnsiConsole.MarkupLine("Messages written to " + AnsiMarkupFormatter.FormatFileName(fileName)); + AnsiConsole.MarkupLine("✅ Messages written to " + AnsiMarkupFormatter.FormatFileName(fileName)); captureWriter.Close(); } From 57ea7d85122ce5f140da20abe23b998e294445c2 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Mon, 15 Jan 2024 19:44:29 -0500 Subject: [PATCH 5/6] Reset midisrv buffer size to PAGE_SIZE --- src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp b/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp index 8496bfd7f..56334c3c0 100644 --- a/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp +++ b/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp @@ -52,8 +52,8 @@ CMidi2MidiSrv::Initialize( // Todo: client side buffering requests to come from some service setting? // - See https://github.com/microsoft/MIDI/issues/219 for details - //creationParams.BufferSize = PAGE_SIZE; - creationParams.BufferSize = 512; // for debugging DON'T LEAVE THIS IN + creationParams.BufferSize = PAGE_SIZE; + //creationParams.BufferSize = 512; // Set this for debugging see https://github.com/microsoft/MIDI/issues/182 for all the drama :) RETURN_IF_FAILED(GetMidiSrvBindingHandle(&bindingHandle)); From 6a2700515adb5832527253687fd2657f4df6a664 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Mon, 15 Jan 2024 20:11:33 -0500 Subject: [PATCH 6/6] Update console with autoreset events for thread signal Greatly reduced CPU usage from 10% of my i9 to 0.5% when monitoring rapidly arriving messages from ProtoZoa, and even more when monitoring slower messages from other sources. --- .../Endpoint/EndpointMonitorCommand.cs | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs index c986820cf..3e14efbac 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs @@ -39,8 +39,9 @@ internal class EndpointMonitorCommand : Command private Queue m_fileWriterMessagesQueue = new Queue(); - // private static AutoResetEvent m_displayMessageQueueNewMessage = new AutoResetEvent(true); - // private static AutoResetEvent m_fileMessageQueueNewMessage = new AutoResetEvent(false); + private static AutoResetEvent m_displayMessageThreadWakeup = new AutoResetEvent(false); + private static AutoResetEvent m_fileMessageThreadWakeup = new AutoResetEvent(false); + private static AutoResetEvent m_terminateMessageListenerThread = new AutoResetEvent(false); @@ -268,12 +269,7 @@ public override int Execute(CommandContext context, Settings settings) } }; - // spin wait. We can change this to an event. - while (continueWaiting && !_hasEndpointDisconnected) - { - Thread.Sleep(0); - } - + m_terminateMessageListenerThread.WaitOne(); }); messageListener.Start(); @@ -283,6 +279,8 @@ public override int Execute(CommandContext context, Settings settings) { while (continueWaiting) { + m_displayMessageThreadWakeup.WaitOne(5000); + if (m_displayMessageQueue.Count > 0) { ReceivedMidiMessage message; @@ -320,7 +318,7 @@ public override int Execute(CommandContext context, Settings settings) displayTable.OutputRow(message, offsetMicroseconds); } - Thread.Sleep(0); + //Thread.Sleep(0); } }); messageConsoleDisplay.Start(); @@ -333,6 +331,8 @@ public override int Execute(CommandContext context, Settings settings) { while (continueWaiting) { + m_fileMessageThreadWakeup.WaitOne(5000); + if (m_fileWriterMessagesQueue.Count > 0) { ReceivedMidiMessage message; @@ -358,7 +358,7 @@ public override int Execute(CommandContext context, Settings settings) } - Thread.Sleep(0); + //Thread.Sleep(0); } }); messageFileWriter.Start(); @@ -376,6 +376,11 @@ public override int Execute(CommandContext context, Settings settings) { continueWaiting = false; + // wake up the threads so they terminate + m_terminateMessageListenerThread.Set(); + m_fileMessageThreadWakeup.Set(); + m_displayMessageThreadWakeup.Set(); + AnsiConsole.WriteLine(); AnsiConsole.MarkupLine("🛑 " + Strings.MonitorEscapePressedMessage); } @@ -385,7 +390,11 @@ public override int Execute(CommandContext context, Settings settings) if (_hasEndpointDisconnected) { continueWaiting = false; - AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(Strings.EndpointDisconnected)); + m_terminateMessageListenerThread.Set(); + m_displayMessageThreadWakeup.Set(); + m_fileMessageThreadWakeup.Set(); + + AnsiConsole.MarkupLine("❎ " + AnsiMarkupFormatter.FormatError(Strings.EndpointDisconnected)); } else if (continueWaiting && m_receivedMessagesQueue.Count > 0) { @@ -405,6 +414,7 @@ public override int Execute(CommandContext context, Settings settings) { m_displayMessageQueue.Enqueue(message); } + m_displayMessageThreadWakeup.Set(); // add to the file writer queue if we're capturing to file if (captureWriter != null) @@ -413,6 +423,7 @@ public override int Execute(CommandContext context, Settings settings) { m_fileWriterMessagesQueue.Enqueue(message); } + m_fileMessageThreadWakeup.Set(); } } @@ -433,8 +444,6 @@ public override int Execute(CommandContext context, Settings settings) session.DisconnectEndpointConnection(connection.ConnectionId); } - - // Summary information if (outOfOrderMessageCount > 0)