question

SAM-7891 avatar image
0 Votes"
SAM-7891 asked SAM-7891 commented

How to measure FPS and CPU Usage on Azure RTOS GUIX with STM32H750B-Disco board

Hello,

After this question that I asked few weeks ago, now my GUI is ready, but I need to measure how good its performance is on my board, eg. FPS, CPU usage and stuffs like that;
I searched in the GUIX documentation but didn't found anything related to FPS or performance measurement,
would be appreciated if anybody helps how to do so, does GUIX has performance measurement itself? Or I need to integrate it manually myself?

Thanks in advance,
Best regards.


azure-rtos
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

KenMaxwell-4349 avatar image
2 Votes"
KenMaxwell-4349 answered

The code formatting didn't come out correct above, I want to be sure you understand the nesting so let me give it another try:

 gx_canvas_drawing_initiate()
    
        for each dial:
               gx_canvas_drawing_initiate()
               dial->gx_widget_draw()
               gx_canvas_drawing_complete()
    
 gx_canvas_drawing_complete()
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

KenMaxwell-4349 avatar image
2 Votes"
KenMaxwell-4349 answered KenMaxwell-4349 edited

I don't really like adding this to gxe_system_canvas_refresh(), because this function is only invoked if the application manually initiates a canvas refresh operation. Typically the canvas refresh operation is not invoked manually, it is invoked internally by GUIX when the GUIX event queue is emptied. When GUIX internally invokes the canvas refresh, it does not go through the error-checking "_gxe" wrapper.

I would like to dive into why the display_driver_drawing_complete isn't giving you accurate results. As you can see this is invoked only when the canvas drawing operation "unwinds" and we are done with a drawing sequence, as the code snippet in canvas_drawing_complete below:

 if (canvas -> gx_canvas_draw_nesting > 0)
 {
     if (display -> gx_display_driver_drawing_complete)
     {
         display -> gx_display_driver_drawing_complete(display, canvas);
     }


If you are getting here multiple times for each drawing sequence, it sounds like you are manually invoking the canvas refresh and you are doing this for each dirty widget, rather than combining all the updates into one canvas refresh? That could slow things down considerably, because we also toggle frame buffers when a canvas refresh sequence is complete. So in your example, if you update three dials individually, that would trigger three frame bugger toggle operations. Better to wrap all three updates into one sequence. So like this in pseudo-code:


 gx_canvas_drawing_initiate()    // mark the beginning
    
      for each dial:
           gx_canvas_drawing_initiate()
           dial->gx_widget_draw()
           gx_canvas_drawing_complete()
    
 gx_canvas_drawing_complete()   // the end, buffer toggle and FPS count


This happens for you automatically if you just mark the widgets dirty and let GUIX do the canvas refresh internally. If you are manually invoking canvas refresh, then you have control over how this happens. I would generally advise just to mark the widgets dirty and let GUIX take care of redrawing what needs to be redrawn, but you could have very good reasons for doing things as you are, every application is different.

Hope this helps,

Ken

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

KenMaxwell-4349 avatar image
2 Votes"
KenMaxwell-4349 answered

drawing_initiate() and drawing_complete() become nested based on how many levels of parent/child widgets are being updated. GUIX keeps track of the nesting level via canvas->gx_canvas_draw_nesting. drawing_inititate() calls the display driver gx_display_driver_drawing_initiate function only on the first entry, and call the display driver gx_display_driver_drawing_complete only on the last exit from these nested functions. So if you put your counter in the display driver callback functions you will get an accurate frame count.

Best Regards,

Ken

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

KenMaxwell-4349 avatar image
1 Vote"
KenMaxwell-4349 answered SAM-7891 commented

Ahh, OK so the "gxe_canvas_refresh" got me a little bit off-track. Yes, your code above looks correct.

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Yes that was my fault, Sorry about that;

Very nice, Many thanks for your assistance in my project, much appreciated.

0 Votes 0 ·
SAM-7891 avatar image
1 Vote"
SAM-7891 answered SAM-7891 edited

@KenMaxwell-4349
Yes, this is correct about parent/child widgets, but for example in my own application, I have a lot of widgets which they aren't parent/child of each other and they have their own animation and have to be redrawn in every frame;

For example In my demo (which is running pretty slow and you can see the gap between frames very easy) If I put the counter in display driver callback (gx_display_driver_drawing_complete and gx_display_driver_drawing_initiate) , It will be incremented for like 200 times in one second! Which is definitely not the actual FPS of my demo; as I said before, with my method that I mentioned above, I calculated my demo's FPS around 15-18 which makes more sense;
and In less complicated demos I'm getting exactly 50 FPS which is compatible with 60 steps animation and 20ms delay between steps.

Now I just need to make sure if my method is correct or not.
Here is a part of "gx_system_canvas_refresh.c" file that I put the counter in it, if you want to take a look at:

                  /* Indicate that drawing on this canvas is complete.  */
                 _gx_canvas_drawing_complete(canvas, GX_FALSE);
             }
             dirty++;
         }
         /* Indicate that drawing on this canvas is complete.  */
         _gx_canvas_drawing_complete(canvas, GX_FALSE);
         // Increment frame counter
         counter++ ;
     }

I just put a "counter++" after second "_gx_canvas_drawing_complete" as you can see, Is that correct?
Again thanks for all your attention and time, I do appreciate it.

Regards.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

KenMaxwell-4349 avatar image
1 Vote"
KenMaxwell-4349 answered SAM-7891 edited

To calculate FPS you really need to key in more on the display driver buffer toggle (equivalent to canvas refresh). I don't know what you are using exactly for display driver, but I believe you are using STM32 display driver? All display drivers can provide callback functions that are invoked at the start and conclusion of each GUIX canvas refresh. For the ST display driver developed in-house, we provide gx_chromeart_drawing_initiate and gx_chromeart_drawing_complete callback functions. These are enabled via a #define (GX_TIMING _ENABLE). The default operation of these callback functions is to toggle a GPIO pin, but you could of course change this to do whatever you like. So it is pretty simple to put a logic analyzer on this GPIO pin to measure the frame rate. If you don't want to go to that trouble you could simply increment a counter and use another timer to measure a 1-second interval.

Keep in mind that the GUIX canvas refresh is driven by the application, not hardware such as an LCD vertical refresh interrupt. So the application really determines, within the performance limits of the target, how often a canvas refresh occurs. If nothing is changing on the display, the refresh rate is 0 FPS. If something is changing based on pen-drag input, the refresh rate will be determined by the touch input driver event generation interval. If you have configured animations, the refresh rate will be determined by the animation timer interval. Again, this assumes the drawing can "keep up" with the animation timer. The upper limit of FPS will be determined by a lot of factors: Display area being updated, color depth, complexity of drawing operations (alpha blending, RLE encoding, etc..) and total available CPU/memory bandwidth.

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thanks for your answer Mr. Maxwell! Nice to see you here.

Yes, I'm using STM32 display driver.
luckily I did almost the same before your answer, I was searching in the codes to find out where should I increment my frame counter, and finally put my counter increment right after where _gx_canvas_drawing_complete() called (after all dirty widgets are drawn) in the _gx_system_canvas_refresh() function; and used "uwTick" for 1-second interval; as result I got around 50 fps for simple demos like "SpeedMotor" (1 circular gauge), And I got between 15-18 fps for my own demo (4 circular gauges) which makes sense; my current challenge is this low amount of FPS I'm getting in my own demo which I made a topic about it here

About your answer, I think _gx_canvas_drawing_complete() is called in two places in _gx_system_canvas_refresh() function, one time after drawing every dirty widget and one time after drawing all the dirty widgets if I'm not mistaking, So if I put the counter increment in the _gx_canvas_drawing_complete() function (or gx_chromeart_drawing_complete function in the other words), I'll count many times more than actual frames count, Is that right?

Thanks for your help again,
Best regards.


0 Votes 0 ·
SatishBoddu-MSFT avatar image
1 Vote"
SatishBoddu-MSFT answered SAM-7891 edited

Hello @SAM-7891, below is the response from the product team, I hope this helps with your initial query.

Execution profile can be used in this case. Please refer to : threadx/tx_execution_profile.h at master · azure-rtos/threadx (github.com)
Between the CPU busy/idle information and the detailed drawing timing information, hopefully this gives you everything you need.



If the response is helpful, please click "Accept Answer" and upvote it and leave your feedback.

For any further help in this matter please comment in the below section.



· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thank you very much for your answer @SatishBoddu-MSFT,
I am working on it and will post my feedback whenever that finishes.


0 Votes 0 ·

Hello again @SatishBoddu-MSFT ,

I completed calculating CPU usage of the GUIX, although its pretty high! (100% for my GUI with 4 circular gauges, 75% for a sample with 1 circular gauge) But I'll ask about that in a different topic;
For now I want to ask how I can measure FPS as well with these Execution profile functions?
I can measure threads and ISRs execution time in addition to system idle time, how it is related to calculation FPS?
And I didn't understand this part of your answer: "detailed drawing timing information", How can I get those information with Execution profile?

Thanks for your time once again,
Best regards.




0 Votes 0 ·
SAM-7891 avatar image
0 Votes"
SAM-7891 answered

@KenMaxwell-4349
Again thanks for all your support, much appreciated;
First of all, I do apologize, but I made a typing mistake in my last post which causes a bit of misunderstanding and effected your answer, unfortunately.

That code snippet which I wrote in my post was from "gx_system_canvas_refresh.c", not "gxe_system_canvas_refresh.c" , I just realized the mistake after your answer about manually initiating a canvas refresh operation, Sorry for that; post is edited it now.

Actually I didn't invoke canvas refresh anywhere myself I think, I didn't modified GUIX files as well, GUIX is doing the drawing processing automatically and I don't even manually mark widgets as dirty myself, This is being done by events.

About non-accurate frame count from display_driver_drawing_complete:

 gx_canvas_drawing_initiate()
     for each dial:
            gx_canvas_drawing_initiate()
            dial->gx_widget_draw()
            gx_canvas_drawing_complete()
 gx_canvas_drawing_complete()

If I have 3 dials In this code, after a full canvas refresh, "gx_canvas_drawing_complete()"called 4 times and the frame counter incremented 4 times as result, But the whole canvas refresh should consider as a single frame, not 4 in this example, And this is the reason I will over-count if I put the counter in gx_display_driver_drawing_complete;
As you know everytime "gx_canvas_drawing_complete()" called, This condition: "canvas -> gx_canvas_draw_nesting > 0" is true (because gx_canvas_drawing_initiate was called before that) and it calls gx_display_driver_drawing_complete function, I tested it with debugger as well.

Finally, according to your pseudo-code, I think it's correct to put the frame counter increment when whole canvas drawing operation is done, and before buffer toggle starts, like this:

 gx_canvas_drawing_initiate()    // mark the beginning
       for each dial:
            gx_canvas_drawing_initiate()
            dial->gx_widget_draw()
            gx_canvas_drawing_complete()
      
  gx_canvas_drawing_complete()   // the end, buffer toggle and FPS count
  Counter++ // increment the frame counter variable

Is this correct now?

Thanks again for spending time on my project,
Best regards.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.