Improve how dotnet-counters shows/truncates attributes (#4539)
authorThays Grazia <thaystg@gmail.com>
Mon, 11 Mar 2024 16:22:51 +0000 (13:22 -0300)
committerGitHub <noreply@github.com>
Mon, 11 Mar 2024 16:22:51 +0000 (12:22 -0400)
As described on #4170 I did 3 things:
- Removed the 80 character horizontal width limit
- 2b - Formatted it as a little inline table with names in the header
and values below
- 2c - Truncate partially each column if needed avoiding the rightmost
columns are get hidden

Sample without this PR:

![image](https://github.com/dotnet/diagnostics/assets/4503299/4f9665a3-a3cf-4d1c-9881-90009cfb7ba1)

Sample with this PR:

![image](https://github.com/dotnet/diagnostics/assets/4503299/aa70757e-7445-477f-a45d-8c5a9fbdd756)

Fixes #4170

src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs
src/tests/dotnet-counters/ConsoleExporterTests.cs

index 954b498ee97e1496171a4e9530a269e05a8b7958..94df5e4dc44945616f45c2229297318f44d3687e 100644 (file)
@@ -61,6 +61,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
         private readonly bool _showDeltaColumn;
         private const int Indent = 4; // Counter name indent size.
         private const int CounterValueLength = 15;
+        private const int MinimalColumnHeaderLength = 5;
 
         private int _nameColumnWidth; // fixed width of the name column. Names will be truncated if needed to fit in this space.
         private int _statusRow; // Row # of where we print the status of dotnet-counters
@@ -127,7 +128,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
             _consoleHeight = _console.WindowHeight;
             // Truncate the name column if needed to prevent line wrapping
             int numValueColumns = _showDeltaColumn ? 2 : 1;
-            _nameColumnWidth = Math.Max(Math.Min(80, _consoleWidth) - numValueColumns * (CounterValueLength + 1), 0);
+            _nameColumnWidth = Math.Max(_consoleWidth - numValueColumns * (CounterValueLength + 1), 0);
 
 
             int row = _console.CursorTop;
@@ -157,7 +158,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
                         counter.Row = row;
                         if (counter.RenderValueInline)
                         {
-                            if (!RenderCounterValueRow(ref row, indentLevel:1, counter.DisplayName, counter.LastValue, 0))
+                            if (!RenderCounterValueRow(ref row, indentLevel: 1, counter.DisplayName, counter.LastValue, 0))
                             {
                                 break;
                             }
@@ -168,13 +169,9 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
                             {
                                 break;
                             }
-                            foreach (ObservedTagSet tagSet in counter.TagSets.Values.OrderBy(t => t.Tags))
+                            if (!RenderTagSetsInColumnMode(ref row, counter))
                             {
-                                tagSet.Row = row;
-                                if (!RenderCounterValueRow(ref row, indentLevel: 2, tagSet.Tags, tagSet.LastValue, 0))
-                                {
-                                    break;
-                                }
+                                break;
                             }
                         }
                     }
@@ -184,6 +181,108 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
             _maxRow = Math.Max(_maxRow, row);
         }
 
+        private bool RenderTagSetsInColumnMode(ref int row, ObservedCounter counter)
+        {
+            List<(string header, string[] values)> observedTags = new();
+            List<int> columnHeaderLen = new();
+            List<int> maxValueColumnLen = new();
+            int tagsCount = 0;
+            foreach (ObservedTagSet tagSet in counter.TagSets.Values.OrderBy(t => t.Tags))
+            {
+                string[] tags = tagSet.DisplayTags.Split(',');
+                for (int i = 0; i < tags.Length; i++)
+                {
+                    string tag = tags[i];
+                    string[] keyValue = tag.Split("=");
+                    int posTag = observedTags.FindIndex (tag => tag.header == keyValue[0]);
+                    if (posTag == -1)
+                    {
+                        observedTags.Add((keyValue[0], new string[counter.TagSets.Count]));
+                        columnHeaderLen.Add(keyValue[0].Length);
+                        maxValueColumnLen.Add(default(int));
+                        posTag = observedTags.Count - 1;
+                    }
+                    observedTags[posTag].values[tagsCount] = keyValue[1];
+                    maxValueColumnLen[posTag] = Math.Max(keyValue[1].Length, maxValueColumnLen[posTag]);
+                }
+                tagsCount++;
+            }
+            AdjustColumnsLength(columnHeaderLen, maxValueColumnLen);
+            //print the header
+            string headerRow = "";
+            for (int i = 0; i < observedTags.Count; i++)
+            {
+                (string header, string[] values) observedTag = observedTags[i];
+                string headerWithSpaces = MakeFixedWidth(observedTag.header, Math.Max(maxValueColumnLen[i], columnHeaderLen[i]), truncateLeft: true);
+                headerWithSpaces += " ";
+                headerRow += headerWithSpaces;
+            }
+            if (!RenderCounterNameRow(ref row, headerRow, indentLevel: 2))
+            {
+                return false;
+            }
+            //print each line
+            int linePos = 0;
+            foreach (ObservedTagSet tagSet in counter.TagSets.Values.OrderBy(t => t.Tags))
+            {
+                tagSet.Row = row;
+                string tagRow = "";
+                for (int i = 0; i < observedTags.Count; i++)
+                {
+                    (string header, string[] values) observedTag = observedTags[i];
+                    string tagItem = observedTag.values[linePos];
+                    string tagWithSpaces = MakeFixedWidth(tagItem, Math.Max(maxValueColumnLen[i], columnHeaderLen[i]));
+                    tagWithSpaces += " ";
+                    tagRow += tagWithSpaces;
+                }
+                if (!RenderCounterValueRow(ref row, indentLevel: 2, tagRow, tagSet.LastValue, 0))
+                {
+                    return false;
+                }
+                linePos++;
+            }
+            return true;
+        }
+        // This method attempts to truncate column header content while prioritizing the actual content.
+        // Initially, we evenly divide the available space among columns, aiming to reduce the size
+        // of each column header until it fits on the screen. If this isn't possible due to headers
+        // reaching a minimal length (MinimalColumnHeaderLength), we then truncate the values
+        // themselves until we achieve the desired width that fits within the screen.
+        private void AdjustColumnsLength(List<int> columnHeaderLen, List<int> maxValueColumnLen)
+        {
+            int totalColumnLength = 0;
+            bool startReduceValueColumnLength = false;
+            for (int i = 0; i < columnHeaderLen.Count; i++)
+            {
+                totalColumnLength += Math.Max(columnHeaderLen[i] + 1, maxValueColumnLen[i] + 1);
+            }
+
+            int needsToReduce = totalColumnLength - (_nameColumnWidth - Indent * 2);
+            while (needsToReduce > 0)
+            {
+                bool changed = false;
+                int needsToReducePerColumn = Math.Max((needsToReduce / columnHeaderLen.Count), 1);
+                for (int i = 0; needsToReduce > 0 && i < columnHeaderLen.Count; i++)
+                {
+                    if (columnHeaderLen[i] > maxValueColumnLen[i] && columnHeaderLen[i] - needsToReducePerColumn > MinimalColumnHeaderLength)
+                    {
+                        columnHeaderLen[i] -= needsToReducePerColumn;
+                        needsToReduce -= needsToReducePerColumn;
+                        changed = true;
+                    }
+                    if (startReduceValueColumnLength)
+                    {
+                        maxValueColumnLen[i] -= needsToReducePerColumn;
+                        needsToReduce -= needsToReducePerColumn;
+                    }
+                }
+                if (!changed) //cannot reduce header anymore, start reducing the value
+                {
+                    startReduceValueColumnLength = true;
+                }
+            }
+        }
+
         public void ToggleStatus(bool pauseCmdSet)
         {
             if (_paused == pauseCmdSet)
@@ -336,9 +435,9 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
             return RenderTableRow(ref row, $"{new string(' ', Indent * indentLevel)}{name}", FormatValue(value), deltaText);
         }
 
-        private bool RenderCounterNameRow(ref int row, string name)
+        private bool RenderCounterNameRow(ref int row, string name, int indentLevel = 1)
         {
-            return RenderTableRow(ref row, $"{new string(' ', Indent)}{name}");
+            return RenderTableRow(ref row, $"{new string(' ', Indent * indentLevel)}{name}");
         }
 
         private bool RenderTableRow(ref int row, string name, string value = null, string delta = null)
@@ -444,7 +543,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
             return valueText;
         }
 
-        private static string MakeFixedWidth(string text, int width, bool alignRight = false)
+        private static string MakeFixedWidth(string text, int width, bool alignRight = false, bool truncateLeft = false)
         {
             if (text == null)
             {
@@ -456,6 +555,10 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
             }
             else if (text.Length > width)
             {
+                if (truncateLeft)
+                {
+                    return text.Substring(text.Length-width, width);
+                }
                 return text.Substring(0, width);
             }
             else
index 381ccc5707ccac96e0b72477ae52efee8f9c516c..1f671c39b6ffbe34299d0e5f9dfbaaf67005478b 100644 (file)
@@ -239,11 +239,13 @@ namespace DotnetCounters.UnitTests
                                      "Name                                 Current Value",
                                      "[Provider1]",
                                      "    Counter1 ({widget} / 1 sec)",
-                                     "        color=blue                          87",
-                                     "        color=red                            0.1",
+                                     "        color",
+                                     "        blue                                87",
+                                     "        red                                  0.1",
                                      "    Counter2 ({widget} / 1 sec)",
-                                     "        size=1                              14",
-                                     "        temp=hot                           160");
+                                     "        size temp",
+                                     "        1                                   14",
+                                     "             hot                           160");
         }
 
         [Fact]
@@ -263,11 +265,13 @@ namespace DotnetCounters.UnitTests
                                      "Name                                 Current Value",
                                      "[Provider1]",
                                      "    Counter1 ({widget} / 1 sec)",
-                                     "        color=blue,LongNameTag=Thi          87",
-                                     "        color=red                            0.1",
+                                     "        color LongNameTag   herOne",
+                                     "        blue  ThisDoesNotFi Hi              87",
+                                     "        red                                  0.1",
                                      "    Counter2 ({widget} / 1 sec)",
-                                     "        size=1                              14",
-                                     "        temp=hot                           160");
+                                     "        size temp",
+                                     "        1                                   14",
+                                     "             hot                           160");
         }
 
         [Fact]
@@ -287,7 +291,7 @@ namespace DotnetCounters.UnitTests
                                      "Name                                 Current Value",
                                      "[Provider1]",
                                      "    Counter1 ({widget} / 1 sec)",
-                                     "        color=blue                          87");
+                                     "        color");
         }
 
         [Fact]
@@ -302,7 +306,6 @@ namespace DotnetCounters.UnitTests
             exporter.CounterPayloadReceived(CreateMeterCounterPreNet8("Provider1", "Counter2", "{widget}", "size=1", 14), false);
             exporter.CounterPayloadReceived(CreateMeterCounterPreNet8("Provider1", "Counter2", "{widget}", "temp=hot", 160), false);
             exporter.SetErrorText("Uh-oh, a bad thing happened");
-
             console.AssertLinesEqual("Press p to pause, r to resume, q to quit.",
                                      "    Status: Running",
                                      "Uh-oh, a bad thing happened",
@@ -310,11 +313,13 @@ namespace DotnetCounters.UnitTests
                                      "Name                                 Current Value",
                                      "[Provider1]",
                                      "    Counter1 ({widget} / 1 sec)",
-                                     "        color=blue                          87",
-                                     "        color=red                            0.1",
+                                     "        color",
+                                     "        blue                                87",
+                                     "        red                                  0.1",
                                      "    Counter2 ({widget} / 1 sec)",
-                                     "        size=1                              14",
-                                     "        temp=hot                           160");
+                                     "        size temp",
+                                     "        1                                   14",
+                                     "             hot                           160");
         }
 
         [Fact]
@@ -338,11 +343,13 @@ namespace DotnetCounters.UnitTests
                                      "    Allocation Rate (B / 1 sec)        1,731",
                                      "[Provider1]",
                                      "    Counter1 ({widget} / 1 sec)",
-                                     "        color=blue                        87",
-                                     "        color=red                          0.1",
+                                     "        color",
+                                     "        blue                              87",
+                                     "        red                                0.1",
                                      "    Counter2 ({widget} / 1 sec)",
-                                     "        size=1                            14",
-                                     "        temp=hot                         160");
+                                     "        size temp",
+                                     "        1                                 14",
+                                     "             hot                         160");
         }
 
         [Fact]
@@ -365,11 +372,13 @@ namespace DotnetCounters.UnitTests
                                      "    Allocation Rate (B / 1 sec)        1,731",
                                      "[Provider1]",
                                      "    Counter1 ({widget} / 1 sec)",
-                                     "        color=blue                        87",
-                                     "        color=red                          0.1",
+                                     "        color",
+                                     "        blue                              87",
+                                     "        red                                0.1",
                                      "    Counter2 ({widget} / 1 sec)",
-                                     "        size=1                            14",
-                                     "        temp=hot                         160");
+                                     "        size temp",
+                                     "        1                                 14",
+                                     "             hot                         160");
 
             exporter.CounterPayloadReceived(CreateIncrementingEventCounter("System.Runtime", "Allocation Rate", "B", 1732), false);
             exporter.CounterPayloadReceived(CreateMeterCounterPreNet8("Provider1", "Counter1", "{widget}", "color=red", 0.2), false);
@@ -383,11 +392,13 @@ namespace DotnetCounters.UnitTests
                                      "    Allocation Rate (B / 1 sec)        1,732               1",
                                      "[Provider1]",
                                      "    Counter1 ({widget} / 1 sec)",
-                                     "        color=blue                        87               0",
-                                     "        color=red                          0.2             0.1",
+                                     "        color",
+                                     "        blue                              87               0",
+                                     "        red                                0.2             0.1",
                                      "    Counter2 ({widget} / 1 sec)",
-                                     "        size=1                            10              -4",
-                                     "        temp=hot                         160");
+                                     "        size temp",
+                                     "        1                                 10              -4",
+                                     "             hot                         160");
         }
 
         // Starting in .NET 8 MetricsEventSource, Meter counter instruments report both rate of change and
@@ -411,8 +422,9 @@ namespace DotnetCounters.UnitTests
                                      "Name                               Current Value      Last Delta",
                                      "[Provider1]",
                                      "    Counter1 ({widget})",                                            // There is no longer (unit / 1 sec) here
-                                     "        color=blue                        87",
-                                     "        color=red                          0.1",
+                                     "        color",
+                                     "        blue                              87",
+                                     "        red                                0.1",
                                      "    Counter2 ({widget})                   14");
 
             exporter.CounterPayloadReceived(CreateMeterCounterPostNet8("Provider1", "Counter1", "{widget}", "color=red", 0.2), false);
@@ -425,8 +437,9 @@ namespace DotnetCounters.UnitTests
                                      "Name                               Current Value      Last Delta",
                                      "[Provider1]",
                                      "    Counter1 ({widget})",                                            // There is no longer (unit / 1 sec) here
-                                     "        color=blue                        87               0",
-                                     "        color=red                          0.2             0.1",
+                                     "        color",
+                                     "        blue                              87               0",
+                                     "        red                                0.2             0.1",
                                      "    Counter2 ({widget})                   10              -4");
         }