Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic scaling & Graph meter coloring (new) #714

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

Explorer09
Copy link
Contributor

@Explorer09 Explorer09 commented Jul 29, 2021

This pull request implements dynamic scaling support and a "stacked" color for Graph meters. In addition, the Bar meter drawing routine is slightly improved for dynamically expanding the "total" value when needed.

This code would replace the current, one-color graph drawing algorithm so that all Graph meters are drawn with colors.

The data structures and algorithms for the color graphs and dynamically scaling are brand new, totally reworked from #129 and the colors are intended to represent the meter data as close as possible. It still uses braille dots to display the graph, but they now have finer details (I think).

Screenshot


Caveats

  • The graph now shows only half the number of records after this PR. The old code used to show two records per terminal column (utilizing the two columns of a braille character), but due to coloring in terminal display, I have to change it to one record per terminal column, both in ASCII and Unicode terminal display.
  • The new code no longer keeps the graph "values" in the C double (floating point) data type. It now stores the precomputed colors and braille dots instead, and the data are specific to a graph height setting.
  • The "GRAPH_1" and "GRAPH_2" colors defined in CRT.h would become unused.

Technical details

  • The main method of determining the colors of all cells (terminal characters) is the largest remainder method. It ensures the color cells chosen would be representative to the original values of a meter. (Just like how it is used in electing representatives in an electoral system)
  • When dynamic scaling and color graph are combined, the graph color computing routine would perform multiple times to render the colors in multiple scales. The "color cells in multiple scales" are then stored in an array with an indexing method mimicking the "in-order traversal" of a complete binary tree. This allows at most 2 × "graph height" cells to be used for storing colors.

The following are potential features that can be implemented after this pull request, but I doubt if they are good ideas or if I have the ability to implement them:

  • A new color for the scale unit text (%, 32K, 16M, etc.), separate from the caption color. I was considering making the text blue to separate it from the cyan text of the caption.
  • Graph height adjustment at runtime. At least allow the htoprc file to specify the graph height. The settings UI would be the next step to implement.
  • A "colorblind" display of the graph, for the "monochrome" color scheme. The bar mode characters (|#*@$%&) can be reused for this.

@BenBE BenBE added the new feature Completely new feature label Jul 29, 2021
Copy link
Member

@BenBE BenBE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick note:

Please split the new functions for PrevPowerOfTwo and PopCount into their own commit. Also try to utilize configure.ac to check for them and provide static inline implementations directly in the header (XUtils.h or Macros.h both should work).

There's an example how to check for specific compiler features in configure.ac to determine whether certain warning flags are supported. You should be able to work based on that example there.

Meter.c Outdated Show resolved Hide resolved
@BenBE
Copy link
Member

BenBE commented Jul 29, 2021

Having the caption above the unit makes more sense IMHO.

@Explorer09
Copy link
Contributor Author

  1. I've done moving the caption to above the unit. It's an easy change.
  2. The code for checking __builtin_clz in configure is also done.
AC_MSG_CHECKING(for __builtin_clz)
AC_COMPILE_IFELSE([
   AC_LANG_PROGRAM([], [__builtin_clz(1); /* Requires gcc 3.4 or later */])],
   [AC_DEFINE([HAVE_BUILTIN_CLZ], 1, [Define to 1 if the compiler supports `__builtin_clz' function.])
   AC_MSG_RESULT(yes)],
   AC_MSG_RESULT(no))
  1. I think I can move the prevPowerOf2 function to XUtils.c, but I don't think it's good idea to make it static inline as the code looks non-trivial.
  2. I am reserved on using builtin_popcount. The reason is, for x86 family, POPCNT requires SSE 4.2 (instruction set) or -msse4.2 compiler flag to be efficient. If POPCNT is not available, gcc emits a libgcc call. My PopCount8 implementation is optimized (for 8-bit input specifically) and is smaller than libgcc's popcount. Do you suggest me to check for processor instruction instead of just check the compiler's popcount builtin?

@BenBE
Copy link
Member

BenBE commented Jul 29, 2021

  1. I've done moving the caption to above the unit. It's an easy change.

k. :)

  1. I think I can move the prevPowerOf2 function to XUtils.c, but I don't think it's good idea to make it static inline as the code looks non-trivial.

As inline typically just a hint to the compiler it can still decide to ignore it. Also most recent optimizers do code folding anyway, thus would remove the parts imported from different code units upon linking. Also there's some more magic going on with linkers nowadays, thus I doubt this is a real issue with recent compilers. Compilers can still opt to ignore the inline anytime and will happily do if the code becomes too complex or too wasteful when inlining (unless forced to).

  1. I am reserved on using builtin_popcount. The reason is, for x86 family, POPCNT requires SSE 4.2 (instruction set) or -msse4.2 compiler flag to be efficient. If POPCNT is not available, gcc emits a libgcc call. My PopCount8 implementation is optimized (for 8-bit input specifically) and is smaller than libgcc's popcount. Do you suggest me to check for processor instruction instead of just check the compiler's popcount builtin?

Similar argument with popcount:
Link to godbolt

For any reasonable recent compiler the libgcc function will likely be optimized enough (and likely even use SSE2 when the system has it available), thus only the first call to such a function will receive a slowdown. Also most architectures by now have a POPCNT instruction available, which likely results in that instruction used on corresponding optimization levels. It may even be, that on higher optimization levels the compiler will replace your implementation by that instruction anyway. Better to convey the intention to the compiler than have it figure things out via pattern matching IMHO.

NB: Modern compilers are usually better in optimizing your code, than you are. Better optimize the algorithm by replacing it with a better one than trying to be smart …

@Explorer09
Copy link
Contributor Author

For any reasonable recent compiler the libgcc function will likely be optimized enough (and likely even use SSE2 when the system has it available), thus only the first call to such a function will receive a slowdown. Also most architectures by now have a POPCNT instruction available, which likely results in that instruction used on corresponding optimization levels. It may even be, that on higher optimization levels the compiler will replace your implementation by that instruction anyway. Better to convey the intention to the compiler than have it figure things out via pattern matching IMHO.

NB: Modern compilers are usually better in optimizing your code, than you are. Better optimize the algorithm by replacing it with a better one than trying to be smart …

If I have not read the disassembly of __popcountdi2 I would not have made the argument in the first place. The problem is gcc doesn't provide the "popcount 8 bit" for me, so I implement one by hand. (The usual "popcount" algorithm works great for 32, 64 and 128 bits but not optimal for 8 bits.) And I really don't like the call to the sub-optimal __popcountdi2 anyway.

@Explorer09
Copy link
Contributor Author

Explorer09 commented Jul 29, 2021

Update: I figured out what to do with the popCount8 problem. Commit amended and rebased. See XUtils.h in commit b160428 to see what I mean.
(Update (v3): 28b0a60)

@BenBE
Copy link
Member

BenBE commented Jul 29, 2021

Did some tests with the prevPowerOfTwo function on godbolt:
Depending on your compiler and architecture and settings any of the three variants at times produces the shortest assembly with my tweaked variant being faster on x86, but slower on ARM, except when CLZ is actually used on ARM, where it depends heavily on the compiler version used.

Similar effects are likely visible with the popcount implementation too.

@Explorer09
Copy link
Contributor Author

Did some tests with the prevPowerOfTwo function on godbolt:
Depending on your compiler and architecture and settings any of the three variants at times produces the shortest assembly with my tweaked variant being faster on x86, but slower on ARM, except when CLZ is actually used on ARM, where it depends heavily on the compiler version used.

prevPowerOf2(0) should (in theory) output 0, not 1.
I've experimented with what you did before, but your code is not equivalent to mine, and you didn't assert(x > 0).

@Explorer09 Explorer09 force-pushed the graph-color-new-4 branch 2 times, most recently from 14e63c2 to e0b6f94 Compare July 30, 2021 10:16
XUtils.c Outdated Show resolved Hide resolved
XUtils.h Outdated Show resolved Hide resolved
XUtils.h Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
@BenBE
Copy link
Member

BenBE commented Jul 30, 2021

@Explorer09 Why write everything onto screen directly instead of using RichString as buffers? Advantage would be that you could abuse double-buffering there to shift the graph by two units (data points), thus with two sets of RichStrings you could just shift everything by one character and just draw the last cell …

@Explorer09
Copy link
Contributor Author

@BenBE

Why write everything onto screen directly instead of using RichString as buffers? Advantage would be that you could abuse double-buffering there to shift the graph by two units (data points), thus with two sets of RichStrings you could just shift everything by one character and just draw the last cell

It would break dynamic scaling. Look at Load average meter for example, the whole graph needs to be redrawn when the scale (or unit) changes.

@BenBE
Copy link
Member

BenBE commented Jul 30, 2021

Sure, but those situations can be tracked/detected …

@Explorer09
Copy link
Contributor Author

@BenBE The second reason for not using a RichString buffer is the use may change color scheme of htop at runtime, and the RichString buffer is not designed to reflect color scheme changes. Detecting when to clean the buffers like the cases above is too much work, and so I consider it doesn't worth the trouble.

@BenBE
Copy link
Member

BenBE commented Jul 30, 2021

On the other hand, re-using existing code makes maintaining the code easier once it got merged. And in the current state I'm not too convinced that these 650 LOC of new code are in a state that makes maintaining them easy enough to justify the complexity in this code. Just skimming over I saw several routines, that are justifiably quite long. And I also understand you are hesitant to refactor major parts of that code after that much effort that went in.

I had some PRs of mine that needed refactoring and bug fixing for about half a year until I could finally merge them. And even then there were small issues that needed to be addressed. So given the amount of code in this PR it should be one priority to ease maintaining the code and another one to have it working. Even if this sound's like I'm a killjoy here both are requirements to proceed with merging (this) new code.

@Explorer09 Explorer09 force-pushed the graph-color-new-4 branch 4 times, most recently from cd46429 to 3485b0d Compare August 2, 2021 03:00
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
Meter.c Outdated Show resolved Hide resolved
@eworm-de
Copy link
Contributor

eworm-de commented Aug 2, 2021

First of all: Thanks a lot for picking up on this again!

The new code works and it gives even more precision.
On the other hand it looks strange in some situations... Not sure I like it that way. After all the meters are just to give a good overview, the latest bit of precision is not needed IMHO.

Either way I am happy if the makes its way into master.

@BenBE BenBE added the needs-rebase Pull request needs to be rebased and conflicts to be resolved label Aug 3, 2021
@Explorer09 Explorer09 force-pushed the graph-color-new-4 branch 2 times, most recently from 0760ab7 to 24bd56e Compare August 3, 2021 15:36
@lgtm-com
Copy link

lgtm-com bot commented Aug 3, 2021

This pull request introduces 2 alerts when merging 24bd56e into ed82ce6 - view on LGTM.com

new alerts:

  • 1 for Multiplication result converted to larger type
  • 1 for Comparison result is always the same

@Explorer09 Explorer09 force-pushed the graph-color-new-4 branch 2 times, most recently from aa10e7c to 17e8f97 Compare August 6, 2021 03:54
Meter.c Outdated Show resolved Hide resolved
@BenBE
Copy link
Member

BenBE commented Apr 3, 2024

@Explorer09: Here another fixup (for dab72f1):

From 861c34bcc64b6462a22aab1680ff8aefcf14863a Mon Sep 17 00:00:00 2001
From: Benny Baumann <[email protected]>
Date: Wed, 3 Apr 2024 20:32:57 +0200
Subject: fixup! Update "total" value for non-percent bar meters

---
 Meter.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/Meter.c b/Meter.c
index 5841231f..3cdaa1b5 100644
--- a/Meter.c
+++ b/Meter.c
@@ -203,11 +203,9 @@ ListItem* Meter_toListItem(const Meter* this, bool moving) {
 
 static double Meter_computeSum(const Meter* this) {
    double sum = sumPositiveValues(this->values, this->curItems);
-   // Prevent rounding to infinity in IEEE 754
-   if (sum > DBL_MAX)
-      return DBL_MAX;
 
-   return sum;
+   // Prevent rounding to infinity in IEEE 754
+   return sum > DBL_MAX ? DBL_MAX : sum;
 }
 
 /* ---------- TextMeterMode ---------- */
-- 
2.43.0

@Explorer09
Copy link
Contributor Author

@Explorer09 I'd prefer the code to be visually a bit more spacious. Like this:

[code omitted]

What do you think?

Extra comment lines and vertical spacing look okay to me. But I am not a fan of extra braces and indents.

Here another fixup (for dab72f1):

I wrote the return lines separately in the hopes for a more visible commenting, but anyway, I can apply this. (It would be simpler to write MINIMUM(DBL_MAX, sum); it generates the same code)

@BenBE
Copy link
Member

BenBE commented Apr 4, 2024

@Explorer09 I'd prefer the code to be visually a bit more spacious. Like this:
[code omitted]
What do you think?

Extra comment lines and vertical spacing look okay to me. But I am not a fan of extra braces and indents.

You could skip those extra indents, but this would require offset to be named uniquely for both blocks. I inserted the blocks to avoid having the usage of the first block bleed into the second one accidentally.

Here another fixup (for dab72f1):

I wrote the return lines separately in the hopes for a more visible commenting, but anyway, I can apply this. (It would be simpler to write MINIMUM(DBL_MAX, sum); it generates the same code)

Fine too.

configure.ac Outdated Show resolved Hide resolved
@BenBE
Copy link
Member

BenBE commented May 2, 2024

@Explorer09 FYI, will likely do #1387 before this one.

@Explorer09 Explorer09 force-pushed the graph-color-new-4 branch 2 times, most recently from 84b4956 to 1e07509 Compare May 2, 2024 21:01
This property distinguishes meters that have a (relatively) fixed
"total" value and meters that do not have a definite maximum value.
The former meters would be drawn as a "100% stacked bar" or "graph", and
the latter would have their "total" values updated automatically in bar
meter mode.

This commit is a prerequisite for the "Graph meter dynamic scaling and
coloring" feature.

Signed-off-by: Kang-Che Sung <[email protected]>
If "isPercentChart" of a meter is false, update its "total" value
automatically. The "total" value should be finite in order to ensure the
division works.

The newly introduced Meter_computeSum() function will be reused by the
"Graph meter dynamic scaling and coloring" feature.

Signed-off-by: Kang-Che Sung <[email protected]>
This is a prerequisite for the "Graph meter dynamic scaling and
coloring" feature.

powerOf2Floor() will utilize __builtin_clz() or stdc_bit_floor_ui()
(__builtin_clz() is preferred) if either is supported.

popCount8() will utilize ARM NEON instructions and x86 POPCNT
instruction if the machine supports either of them.

I am not adopting the C23 standard interface stdc_count_ones_uc() yet,
as I am not sure C libraries would implement it as fast as our version.

Signed-off-by: Kang-Che Sung <[email protected]>
Rewrite the entire graph meter drawing code to support dynamic scaling
and colors.

Dynamic scaling is used for meter classes with "isPercentChart" set to
false. The "total" of a graph meter will align to a power of 2 to ease
the management of internal data structures.

The colors of a graph are based on the percentages of item values of
the meter. The rounding differences of each terminal character are
addressed through the different numbers of braille dots.

Due to low resolution of the character terminal, the rasterized colors
may not look nice, but better than nothing. :)

The code is designed with the anticipation that the graph height may be
changeable at runtime. However no UI or option has been implemented for
that yet. The graph height has a limit of 8191 (terminal rows).

Signed-off-by: Kang-Che Sung <[email protected]>
Specifically 'PIXPERROW_*' and 'GraphMeterMode_dots*' constants.
They were commented out rather than removed in the previous commit (for
ease of code reviewing). Now this commit removes the constant defines
for good.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-rebase Pull request needs to be rebased and conflicts to be resolved new feature Completely new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants