public inbox for [email protected]
help / color / mirror / Atom feedPgadmin4 System Stats Extension Design
106+ messages / 6 participants
[nested] [flat]
* Pgadmin4 System Stats Extension Design
@ 2023-06-13 09:59 Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-13 09:59 UTC (permalink / raw)
To: pgadmin-hackers
Dear all,
I am working on pgadmin4 to let users see their system-level statistics on
the dashboard. In this mail, I've attached the wireframe to display system
stats on the existing dashboard.
I am open to hearing your thoughts and suggestions on the design.
Thanks,
Sahil
Attachments:
[application/pdf] Pgadmin4 - System Stats - Design 1.pdf (816.1K, 3-Pgadmin4%20-%20System%20Stats%20-%20Design%201.pdf)
download
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-14 04:42 Akshay Joshi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Akshay Joshi @ 2023-06-14 04:42 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
Hi Sahil
At first glance, it looks good to me. Seems you have created a new tab
System Statistics instead of using the existing dashboard.
On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <[email protected]>
wrote:
> Dear all,
>
> I am working on pgadmin4 to let users see their system-level statistics on
> the dashboard. In this mail, I've attached the wireframe to display system
> stats on the existing dashboard.
>
> I am open to hearing your thoughts and suggestions on the design.
>
> Thanks,
> Sahil
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-14 20:11 Sahil Harpal <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-14 20:11 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers
Thank you, Akshay, for your feedback.
Here are a few more designs that I have created based on the discussion
with my mentors. I would love to know your thoughts on them.
Design 1 - Using an additional new tab for system statistics
Design 2 - Added buttons to toggle between existing dashboard data and
system statistics.
Thanks,
Sahil
On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <[email protected]>
wrote:
> Hi Sahil
>
> At first glance, it looks good to me. Seems you have created a new tab
> System Statistics instead of using the existing dashboard.
>
> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <[email protected]>
> wrote:
>
>> Dear all,
>>
>> I am working on pgadmin4 to let users see their system-level statistics
>> on the dashboard. In this mail, I've attached the wireframe to display
>> system stats on the existing dashboard.
>>
>> I am open to hearing your thoughts and suggestions on the design.
>>
>> Thanks,
>> Sahil
>>
>
Attachments:
[application/pdf] System Stats Design 1 with additional button in main panel for system stats.pdf (1.2M, 3-System%20Stats%20Design%201%20with%20additional%20button%20in%20main%20panel%20for%20system%20stats.pdf)
download
[application/pdf] System Stats Design 2 with buttons to toggle between existing dashboard data and system statistics.pdf (1.2M, 4-System%20Stats%20Design%202%20with%20buttons%20to%20toggle%20between%20existing%20dashboard%20data%20and%20system%20statistics.pdf)
download
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-15 05:37 Akshay Joshi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Akshay Joshi @ 2023-06-15 05:37 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
Hi Sahil
On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <[email protected]>
wrote:
> Thank you, Akshay, for your feedback.
> Here are a few more designs that I have created based on the discussion
> with my mentors. I would love to know your thoughts on them.
>
> Design 1 - Using an additional new tab for system statistics
> Design 2 - Added buttons to toggle between existing dashboard data and
> system statistics.
>
I personally like Design 2 as we have only one main tab "Dashboard" and
then two sub-tabs "General"(Can be changed) and "System Statistics".
>
> Thanks,
> Sahil
>
>
>
>
> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <[email protected]>
> wrote:
>
>> Hi Sahil
>>
>> At first glance, it looks good to me. Seems you have created a new tab
>> System Statistics instead of using the existing dashboard.
>>
>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Dear all,
>>>
>>> I am working on pgadmin4 to let users see their system-level statistics
>>> on the dashboard. In this mail, I've attached the wireframe to display
>>> system stats on the existing dashboard.
>>>
>>> I am open to hearing your thoughts and suggestions on the design.
>>>
>>> Thanks,
>>> Sahil
>>>
>>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-15 05:51 Aditya Toshniwal <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-06-15 05:51 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Sahil Harpal <[email protected]>; pgadmin-hackers
Hi Sahil,
I would suggest club OS, CPU, Process, Disk and I/O in a tabbed control.
(Taking inspiration from the task manager).
It will reduce the network calls, cluttering and improve DOM performance.
On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <[email protected]>
wrote:
> Hi Sahil
>
> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <[email protected]>
> wrote:
>
>> Thank you, Akshay, for your feedback.
>> Here are a few more designs that I have created based on the discussion
>> with my mentors. I would love to know your thoughts on them.
>>
>> Design 1 - Using an additional new tab for system statistics
>> Design 2 - Added buttons to toggle between existing dashboard data and
>> system statistics.
>>
>
> I personally like Design 2 as we have only one main tab "Dashboard"
> and then two sub-tabs "General"(Can be changed) and "System Statistics".
>
>>
>> Thanks,
>> Sahil
>>
>>
>>
>>
>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <[email protected]>
>> wrote:
>>
>>> Hi Sahil
>>>
>>> At first glance, it looks good to me. Seems you have created a new tab
>>> System Statistics instead of using the existing dashboard.
>>>
>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Dear all,
>>>>
>>>> I am working on pgadmin4 to let users see their system-level statistics
>>>> on the dashboard. In this mail, I've attached the wireframe to display
>>>> system stats on the existing dashboard.
>>>>
>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>
>>>> Thanks,
>>>> Sahil
>>>>
>>>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-15 08:55 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-15 08:55 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers
Hi Aditya,
Thank you for pointing this out. It would also be more convenient for users
to navigate to specific statistics easily.
So, can we finalise the following design?
- Single dashboard with buttons to toggle between General (existing
graphs/stats) and System Statistics.
- Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
Thanks,
Sahil
On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed control.
> (Taking inspiration from the task manager).
> It will reduce the network calls, cluttering and improve DOM performance.
>
>
> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
> [email protected]> wrote:
>
>> Hi Sahil
>>
>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Thank you, Akshay, for your feedback.
>>> Here are a few more designs that I have created based on the discussion
>>> with my mentors. I would love to know your thoughts on them.
>>>
>>> Design 1 - Using an additional new tab for system statistics
>>> Design 2 - Added buttons to toggle between existing dashboard data and
>>> system statistics.
>>>
>>
>> I personally like Design 2 as we have only one main tab "Dashboard"
>> and then two sub-tabs "General"(Can be changed) and "System Statistics".
>>
>>>
>>> Thanks,
>>> Sahil
>>>
>>>
>>>
>>>
>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil
>>>>
>>>> At first glance, it looks good to me. Seems you have created a new tab
>>>> System Statistics instead of using the existing dashboard.
>>>>
>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Dear all,
>>>>>
>>>>> I am working on pgadmin4 to let users see their system-level
>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>> display system stats on the existing dashboard.
>>>>>
>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>
>>>>> Thanks,
>>>>> Sahil
>>>>>
>>>>
>
> --
> Thanks,
> Aditya Toshniwal
> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
> <https://www.enterprisedb.com/;
> "Don't Complain about Heat, Plant a TREE"
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-15 09:52 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Dave Page @ 2023-06-15 09:52 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers
On Thu, 15 Jun 2023 at 09:55, Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
>
> Thank you for pointing this out. It would also be more convenient for
> users to navigate to specific statistics easily.
>
> So, can we finalise the following design?
> - Single dashboard with buttons to toggle between General (existing
> graphs/stats) and System Statistics.
>
Why use buttons and not tabs? Tabs are far more flexible as they can be
re-arranged, docked differently etc.
> - Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
>
I think the current design has too much on one big page, so yes, I'd want
to see those split up onto different tabs. Not sure about the grouping
though. Maybe:
Summary (OS info, system specs etc)
CPU
Memory
Storage (including I/O)
Process info would be included on each tab as related to that tab's content
- e.g. CPU per process on the CPU tab, memory per process on the memory
tab, etc..)
> Thanks,
> Sahil
>
>
> On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>>
>> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed control.
>> (Taking inspiration from the task manager).
>> It will reduce the network calls, cluttering and improve DOM performance.
>>
>>
>> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Sahil
>>>
>>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Thank you, Akshay, for your feedback.
>>>> Here are a few more designs that I have created based on the discussion
>>>> with my mentors. I would love to know your thoughts on them.
>>>>
>>>> Design 1 - Using an additional new tab for system statistics
>>>> Design 2 - Added buttons to toggle between existing dashboard data and
>>>> system statistics.
>>>>
>>>
>>> I personally like Design 2 as we have only one main tab "Dashboard"
>>> and then two sub-tabs "General"(Can be changed) and "System Statistics".
>>>
>>>>
>>>> Thanks,
>>>> Sahil
>>>>
>>>>
>>>>
>>>>
>>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Sahil
>>>>>
>>>>> At first glance, it looks good to me. Seems you have created a new tab
>>>>> System Statistics instead of using the existing dashboard.
>>>>>
>>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Dear all,
>>>>>>
>>>>>> I am working on pgadmin4 to let users see their system-level
>>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>>> display system stats on the existing dashboard.
>>>>>>
>>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>>
>>>>>> Thanks,
>>>>>> Sahil
>>>>>>
>>>>>
>>
>> --
>> Thanks,
>> Aditya Toshniwal
>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>> <https://www.enterprisedb.com/;
>> "Don't Complain about Heat, Plant a TREE"
>>
>
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-15 10:07 Khushboo Vashi <[email protected]>
parent: Dave Page <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-06-15 10:07 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Sahil Harpal <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers
Regarding I/O analysis, what would be more beneficial, combining total
read/total write etc., per disk OR Sahil doing a different graph for each
parameter for all the disks?
On Thu, Jun 15, 2023 at 3:22 PM Dave Page <[email protected]> wrote:
>
>
> On Thu, 15 Jun 2023 at 09:55, Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Aditya,
>>
>> Thank you for pointing this out. It would also be more convenient for
>> users to navigate to specific statistics easily.
>>
>> So, can we finalise the following design?
>> - Single dashboard with buttons to toggle between General (existing
>> graphs/stats) and System Statistics.
>>
>
> Why use buttons and not tabs? Tabs are far more flexible as they can be
> re-arranged, docked differently etc.
>
>
>> - Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
>>
>
> I think the current design has too much on one big page, so yes, I'd want
> to see those split up onto different tabs. Not sure about the grouping
> though. Maybe:
>
> Summary (OS info, system specs etc)
> CPU
> Memory
> Storage (including I/O)
>
> Process info would be included on each tab as related to that tab's
> content - e.g. CPU per process on the CPU tab, memory per process on the
> memory tab, etc..)
>
>
>> Thanks,
>> Sahil
>>
>>
>> On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> Hi Sahil,
>>>
>>> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed control.
>>> (Taking inspiration from the task manager).
>>> It will reduce the network calls, cluttering and improve DOM performance.
>>>
>>>
>>> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil
>>>>
>>>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Thank you, Akshay, for your feedback.
>>>>> Here are a few more designs that I have created based on the
>>>>> discussion with my mentors. I would love to know your thoughts on them.
>>>>>
>>>>> Design 1 - Using an additional new tab for system statistics
>>>>> Design 2 - Added buttons to toggle between existing dashboard data and
>>>>> system statistics.
>>>>>
>>>>
>>>> I personally like Design 2 as we have only one main tab "Dashboard"
>>>> and then two sub-tabs "General"(Can be changed) and "System Statistics".
>>>>
>>>>>
>>>>> Thanks,
>>>>> Sahil
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Sahil
>>>>>>
>>>>>> At first glance, it looks good to me. Seems you have created a new
>>>>>> tab System Statistics instead of using the existing dashboard.
>>>>>>
>>>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Dear all,
>>>>>>>
>>>>>>> I am working on pgadmin4 to let users see their system-level
>>>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>>>> display system stats on the existing dashboard.
>>>>>>>
>>>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Sahil
>>>>>>>
>>>>>>
>>>
>>> --
>>> Thanks,
>>> Aditya Toshniwal
>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>> <https://www.enterprisedb.com/;
>>> "Don't Complain about Heat, Plant a TREE"
>>>
>>
>
> --
> Dave Page
> Blog: https://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EDB: https://www.enterprisedb.com
>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-15 10:57 Dave Page <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Dave Page @ 2023-06-15 10:57 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: Sahil Harpal <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers
On Thu, 15 Jun 2023 at 11:07, Khushboo Vashi <
[email protected]> wrote:
> Regarding I/O analysis, what would be more beneficial, combining total
> read/total write etc., per disk OR Sahil doing a different graph for each
> parameter for all the disks?
>
I think combining R/W is fine, as long as it uses two scales in case the
values are wildly different (which is likely).
>
> On Thu, Jun 15, 2023 at 3:22 PM Dave Page <[email protected]> wrote:
>
>>
>>
>> On Thu, 15 Jun 2023 at 09:55, Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hi Aditya,
>>>
>>> Thank you for pointing this out. It would also be more convenient for
>>> users to navigate to specific statistics easily.
>>>
>>> So, can we finalise the following design?
>>> - Single dashboard with buttons to toggle between General (existing
>>> graphs/stats) and System Statistics.
>>>
>>
>> Why use buttons and not tabs? Tabs are far more flexible as they can be
>> re-arranged, docked differently etc.
>>
>>
>>> - Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
>>>
>>
>> I think the current design has too much on one big page, so yes, I'd want
>> to see those split up onto different tabs. Not sure about the grouping
>> though. Maybe:
>>
>> Summary (OS info, system specs etc)
>> CPU
>> Memory
>> Storage (including I/O)
>>
>> Process info would be included on each tab as related to that tab's
>> content - e.g. CPU per process on the CPU tab, memory per process on the
>> memory tab, etc..)
>>
>>
>>> Thanks,
>>> Sahil
>>>
>>>
>>> On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil,
>>>>
>>>> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed
>>>> control. (Taking inspiration from the task manager).
>>>> It will reduce the network calls, cluttering and improve DOM
>>>> performance.
>>>>
>>>>
>>>> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Sahil
>>>>>
>>>>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Thank you, Akshay, for your feedback.
>>>>>> Here are a few more designs that I have created based on the
>>>>>> discussion with my mentors. I would love to know your thoughts on them.
>>>>>>
>>>>>> Design 1 - Using an additional new tab for system statistics
>>>>>> Design 2 - Added buttons to toggle between existing dashboard data
>>>>>> and system statistics.
>>>>>>
>>>>>
>>>>> I personally like Design 2 as we have only one main tab
>>>>> "Dashboard" and then two sub-tabs "General"(Can be changed) and "System
>>>>> Statistics".
>>>>>
>>>>>>
>>>>>> Thanks,
>>>>>> Sahil
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Sahil
>>>>>>>
>>>>>>> At first glance, it looks good to me. Seems you have created a new
>>>>>>> tab System Statistics instead of using the existing dashboard.
>>>>>>>
>>>>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Dear all,
>>>>>>>>
>>>>>>>> I am working on pgadmin4 to let users see their system-level
>>>>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>>>>> display system stats on the existing dashboard.
>>>>>>>>
>>>>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Sahil
>>>>>>>>
>>>>>>>
>>>>
>>>> --
>>>> Thanks,
>>>> Aditya Toshniwal
>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>> <https://www.enterprisedb.com/;
>>>> "Don't Complain about Heat, Plant a TREE"
>>>>
>>>
>>
>> --
>> Dave Page
>> Blog: https://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EDB: https://www.enterprisedb.com
>>
>>
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-16 08:45 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-16 08:45 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers
>
> I think combining R/W is fine, as long as it uses two scales in case the
> values are wildly different (which is likely).
So should I combine all the parameters (Total read/write operations, Total
Bytes read/write, and time spent) in a single graph for one disk or just
the total number of read/write operations?
[image: Mailtrack]
<https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...;
Sender
notified by
Mailtrack
<https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...;
16/06/23,
14:14:28
On Thu, 15 Jun 2023 at 16:27, Dave Page <[email protected]> wrote:
>
>
> On Thu, 15 Jun 2023 at 11:07, Khushboo Vashi <
> [email protected]> wrote:
>
>> Regarding I/O analysis, what would be more beneficial, combining total
>> read/total write etc., per disk OR Sahil doing a different graph for each
>> parameter for all the disks?
>>
>
> I think combining R/W is fine, as long as it uses two scales in case the
> values are wildly different (which is likely).
>
>
>>
>> On Thu, Jun 15, 2023 at 3:22 PM Dave Page <[email protected]> wrote:
>>
>>>
>>>
>>> On Thu, 15 Jun 2023 at 09:55, Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Hi Aditya,
>>>>
>>>> Thank you for pointing this out. It would also be more convenient for
>>>> users to navigate to specific statistics easily.
>>>>
>>>> So, can we finalise the following design?
>>>> - Single dashboard with buttons to toggle between General (existing
>>>> graphs/stats) and System Statistics.
>>>>
>>>
>>> Why use buttons and not tabs? Tabs are far more flexible as they can be
>>> re-arranged, docked differently etc.
>>>
>>>
>>>> - Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
>>>>
>>>
>>> I think the current design has too much on one big page, so yes, I'd
>>> want to see those split up onto different tabs. Not sure about the grouping
>>> though. Maybe:
>>>
>>> Summary (OS info, system specs etc)
>>> CPU
>>> Memory
>>> Storage (including I/O)
>>>
>>> Process info would be included on each tab as related to that tab's
>>> content - e.g. CPU per process on the CPU tab, memory per process on the
>>> memory tab, etc..)
>>>
>>>
>>>> Thanks,
>>>> Sahil
>>>>
>>>>
>>>> On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Sahil,
>>>>>
>>>>> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed
>>>>> control. (Taking inspiration from the task manager).
>>>>> It will reduce the network calls, cluttering and improve DOM
>>>>> performance.
>>>>>
>>>>>
>>>>> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Sahil
>>>>>>
>>>>>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Thank you, Akshay, for your feedback.
>>>>>>> Here are a few more designs that I have created based on the
>>>>>>> discussion with my mentors. I would love to know your thoughts on them.
>>>>>>>
>>>>>>> Design 1 - Using an additional new tab for system statistics
>>>>>>> Design 2 - Added buttons to toggle between existing dashboard data
>>>>>>> and system statistics.
>>>>>>>
>>>>>>
>>>>>> I personally like Design 2 as we have only one main tab
>>>>>> "Dashboard" and then two sub-tabs "General"(Can be changed) and "System
>>>>>> Statistics".
>>>>>>
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Sahil
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Sahil
>>>>>>>>
>>>>>>>> At first glance, it looks good to me. Seems you have created a new
>>>>>>>> tab System Statistics instead of using the existing dashboard.
>>>>>>>>
>>>>>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Dear all,
>>>>>>>>>
>>>>>>>>> I am working on pgadmin4 to let users see their system-level
>>>>>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>>>>>> display system stats on the existing dashboard.
>>>>>>>>>
>>>>>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>>>>>
>>>>>>>>> Thanks,
>>>>>>>>> Sahil
>>>>>>>>>
>>>>>>>>
>>>>>
>>>>> --
>>>>> Thanks,
>>>>> Aditya Toshniwal
>>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>>> <https://www.enterprisedb.com/;
>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>
>>>>
>>>
>>> --
>>> Dave Page
>>> Blog: https://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EDB: https://www.enterprisedb.com
>>>
>>>
>
> --
> Dave Page
> Blog: https://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EDB: https://www.enterprisedb.com
>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-17 09:01 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-17 09:01 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
Hi Dave,
Could you please confirm whether we can proceed with the following grouping?
*1. Summary*
- OS information
- Sys CPU Information
- Sys Process Information
*2. CPU*
- Sys CPU Usage Information
- Sys Load Avg Information
- Process Name/Pid - CPU Usage (From Process)
*3. Memory*
- Sys Memory Information
- Process Name/Pid - Memory Usage and Memory Bytes (From Process)
*4. Storage (including I/O)*
- Sys Disk Information
- Sys I/O Analysis Information
I have also attached the dashboard layout, as you suggested.
Thanks,
Sahil
On Thu, 15 Jun 2023 at 15:22, Dave Page <[email protected]> wrote:
>
>
> On Thu, 15 Jun 2023 at 09:55, Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Aditya,
>>
>> Thank you for pointing this out. It would also be more convenient for
>> users to navigate to specific statistics easily.
>>
>> So, can we finalise the following design?
>> - Single dashboard with buttons to toggle between General (existing
>> graphs/stats) and System Statistics.
>>
>
> Why use buttons and not tabs? Tabs are far more flexible as they can be
> re-arranged, docked differently etc.
>
>
>> - Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
>>
>
> I think the current design has too much on one big page, so yes, I'd want
> to see those split up onto different tabs. Not sure about the grouping
> though. Maybe:
>
> Summary (OS info, system specs etc)
> CPU
> Memory
> Storage (including I/O)
>
> Process info would be included on each tab as related to that tab's
> content - e.g. CPU per process on the CPU tab, memory per process on the
> memory tab, etc..)
>
>
>> Thanks,
>> Sahil
>>
>>
>> On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> Hi Sahil,
>>>
>>> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed control.
>>> (Taking inspiration from the task manager).
>>> It will reduce the network calls, cluttering and improve DOM performance.
>>>
>>>
>>> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil
>>>>
>>>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Thank you, Akshay, for your feedback.
>>>>> Here are a few more designs that I have created based on the
>>>>> discussion with my mentors. I would love to know your thoughts on them.
>>>>>
>>>>> Design 1 - Using an additional new tab for system statistics
>>>>> Design 2 - Added buttons to toggle between existing dashboard data and
>>>>> system statistics.
>>>>>
>>>>
>>>> I personally like Design 2 as we have only one main tab "Dashboard"
>>>> and then two sub-tabs "General"(Can be changed) and "System Statistics".
>>>>
>>>>>
>>>>> Thanks,
>>>>> Sahil
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Sahil
>>>>>>
>>>>>> At first glance, it looks good to me. Seems you have created a new
>>>>>> tab System Statistics instead of using the existing dashboard.
>>>>>>
>>>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Dear all,
>>>>>>>
>>>>>>> I am working on pgadmin4 to let users see their system-level
>>>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>>>> display system stats on the existing dashboard.
>>>>>>>
>>>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Sahil
>>>>>>>
>>>>>>
>>>
>>> --
>>> Thanks,
>>> Aditya Toshniwal
>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>> <https://www.enterprisedb.com/;
>>> "Don't Complain about Heat, Plant a TREE"
>>>
>>
>
> --
> Dave Page
> Blog: https://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EDB: https://www.enterprisedb.com
>
>
Attachments:
[image/png] pgadmin4-dashboard-layout.png (59.3K, 3-pgadmin4-dashboard-layout.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-19 08:37 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Dave Page @ 2023-06-19 08:37 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
On Sat, 17 Jun 2023 at 10:01, Sahil Harpal <[email protected]>
wrote:
> Hi Dave,
>
> Could you please confirm whether we can proceed with the following
> grouping?
>
> *1. Summary*
>
> - OS information
> - Sys CPU Information
> - Sys Process Information
>
> *2. CPU*
>
> - Sys CPU Usage Information
> - Sys Load Avg Information
> - Process Name/Pid - CPU Usage (From Process)
>
> *3. Memory*
>
> - Sys Memory Information
> - Process Name/Pid - Memory Usage and Memory Bytes (From Process)
>
> *4. Storage (including I/O)*
>
> - Sys Disk Information
> - Sys I/O Analysis Information
>
>
> I have also attached the dashboard layout, as you suggested.
>
Seems reasonable to me. A wireframe would seem like the best next step, to
confirm we're all happy with what's proposed. It's hard to visualise from a
list of bullet points.
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-19 08:49 Ashesh Vashi <[email protected]>
parent: Dave Page <[email protected]>
1 sibling, 0 replies; 106+ messages in thread
From: Ashesh Vashi @ 2023-06-19 08:49 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Sahil Harpal <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
On Mon, Jun 19, 2023 at 2:07 PM Dave Page <[email protected]> wrote:
>
>
> On Sat, 17 Jun 2023 at 10:01, Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Dave,
>>
>> Could you please confirm whether we can proceed with the following
>> grouping?
>>
>> *1. Summary*
>>
>> - OS information
>> - Sys CPU Information
>> - Sys Process Information
>>
>> *2. CPU*
>>
>> - Sys CPU Usage Information
>> - Sys Load Avg Information
>> - Process Name/Pid - CPU Usage (From Process)
>>
>> *3. Memory*
>>
>> - Sys Memory Information
>> - Process Name/Pid - Memory Usage and Memory Bytes (From Process)
>>
>> *4. Storage (including I/O)*
>>
>> - Sys Disk Information
>> - Sys I/O Analysis Information
>>
>> Can we please remove the 'Sys' prefix from these chart titles? (It looks
redundant to me).
>
>> I have also attached the dashboard layout, as you suggested.
>>
>
> Seems reasonable to me. A wireframe would seem like the best next step, to
> confirm we're all happy with what's proposed. It's hard to visualise from a
> list of bullet points.
>
--
Ashesh Vashi
>
>
>
> --
> Dave Page
> Blog: https://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EDB: https://www.enterprisedb.com
>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-19 14:29 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-19 14:29 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: pgadmin-hackers
On Fri, 16 Jun 2023 at 14:15, Sahil Harpal <[email protected]>
wrote:
> [image: image.gif]
>
>> I think combining R/W is fine, as long as it uses two scales in case the
>> values are wildly different (which is likely).
>
>
> So should I combine all the parameters (Total read/write operations, Total
> Bytes read/write, and time spent) in a single graph for one disk or just
> the total number of read/write operations?
>
Could you please also clarify this?
On Fri, 16 Jun 2023 at 14:15, Sahil Harpal <[email protected]>
wrote:
> I think combining R/W is fine, as long as it uses two scales in case the
>> values are wildly different (which is likely).
>
>
> So should I combine all the parameters (Total read/write operations, Total
> Bytes read/write, and time spent) in a single graph for one disk or just
> the total number of read/write operations?
> [image: Mailtrack]
> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
> notified by
> Mailtrack
> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 16/06/23,
> 14:14:28
>
> On Thu, 15 Jun 2023 at 16:27, Dave Page <[email protected]> wrote:
>
>>
>>
>> On Thu, 15 Jun 2023 at 11:07, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>> Regarding I/O analysis, what would be more beneficial, combining total
>>> read/total write etc., per disk OR Sahil doing a different graph for each
>>> parameter for all the disks?
>>>
>>
>> I think combining R/W is fine, as long as it uses two scales in case the
>> values are wildly different (which is likely).
>>
>>
>>>
>>> On Thu, Jun 15, 2023 at 3:22 PM Dave Page <[email protected]> wrote:
>>>
>>>>
>>>>
>>>> On Thu, 15 Jun 2023 at 09:55, Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Hi Aditya,
>>>>>
>>>>> Thank you for pointing this out. It would also be more convenient for
>>>>> users to navigate to specific statistics easily.
>>>>>
>>>>> So, can we finalise the following design?
>>>>> - Single dashboard with buttons to toggle between General (existing
>>>>> graphs/stats) and System Statistics.
>>>>>
>>>>
>>>> Why use buttons and not tabs? Tabs are far more flexible as they can be
>>>> re-arranged, docked differently etc.
>>>>
>>>>
>>>>> - Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
>>>>>
>>>>
>>>> I think the current design has too much on one big page, so yes, I'd
>>>> want to see those split up onto different tabs. Not sure about the grouping
>>>> though. Maybe:
>>>>
>>>> Summary (OS info, system specs etc)
>>>> CPU
>>>> Memory
>>>> Storage (including I/O)
>>>>
>>>> Process info would be included on each tab as related to that tab's
>>>> content - e.g. CPU per process on the CPU tab, memory per process on the
>>>> memory tab, etc..)
>>>>
>>>>
>>>>> Thanks,
>>>>> Sahil
>>>>>
>>>>>
>>>>> On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Sahil,
>>>>>>
>>>>>> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed
>>>>>> control. (Taking inspiration from the task manager).
>>>>>> It will reduce the network calls, cluttering and improve DOM
>>>>>> performance.
>>>>>>
>>>>>>
>>>>>> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Sahil
>>>>>>>
>>>>>>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Thank you, Akshay, for your feedback.
>>>>>>>> Here are a few more designs that I have created based on the
>>>>>>>> discussion with my mentors. I would love to know your thoughts on them.
>>>>>>>>
>>>>>>>> Design 1 - Using an additional new tab for system statistics
>>>>>>>> Design 2 - Added buttons to toggle between existing dashboard data
>>>>>>>> and system statistics.
>>>>>>>>
>>>>>>>
>>>>>>> I personally like Design 2 as we have only one main tab
>>>>>>> "Dashboard" and then two sub-tabs "General"(Can be changed) and "System
>>>>>>> Statistics".
>>>>>>>
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Sahil
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Sahil
>>>>>>>>>
>>>>>>>>> At first glance, it looks good to me. Seems you have created a new
>>>>>>>>> tab System Statistics instead of using the existing dashboard.
>>>>>>>>>
>>>>>>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Dear all,
>>>>>>>>>>
>>>>>>>>>> I am working on pgadmin4 to let users see their system-level
>>>>>>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>>>>>>> display system stats on the existing dashboard.
>>>>>>>>>>
>>>>>>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>>>>>>
>>>>>>>>>> Thanks,
>>>>>>>>>> Sahil
>>>>>>>>>>
>>>>>>>>>
>>>>>>
>>>>>> --
>>>>>> Thanks,
>>>>>> Aditya Toshniwal
>>>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>>>> <https://www.enterprisedb.com/;
>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>
>>>>>
>>>>
>>>> --
>>>> Dave Page
>>>> Blog: https://pgsnake.blogspot.com
>>>> Twitter: @pgsnake
>>>>
>>>> EDB: https://www.enterprisedb.com
>>>>
>>>>
>>
>> --
>> Dave Page
>> Blog: https://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EDB: https://www.enterprisedb.com
>>
>>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-19 14:33 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 0 replies; 106+ messages in thread
From: Dave Page @ 2023-06-19 14:33 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
On Mon, 19 Jun 2023 at 15:30, Sahil Harpal <[email protected]>
wrote:
> On Fri, 16 Jun 2023 at 14:15, Sahil Harpal <[email protected]>
> wrote:
>
>> [image: image.gif]
>>
>>> I think combining R/W is fine, as long as it uses two scales in case the
>>> values are wildly different (which is likely).
>>
>>
>> So should I combine all the parameters (Total read/write operations,
>> Total Bytes read/write, and time spent) in a single graph for one disk or
>> just the total number of read/write operations?
>>
>
> Could you please also clarify this?
>
Don't combine ops/time/bytes etc, but you can combine read/write,
input/output etc. as we do on the existing graphs.
>
>
> On Fri, 16 Jun 2023 at 14:15, Sahil Harpal <[email protected]>
> wrote:
>
>> I think combining R/W is fine, as long as it uses two scales in case the
>>> values are wildly different (which is likely).
>>
>>
>> So should I combine all the parameters (Total read/write operations,
>> Total Bytes read/write, and time spent) in a single graph for one disk or
>> just the total number of read/write operations?
>> [image: Mailtrack]
>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
>> notified by
>> Mailtrack
>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 16/06/23,
>> 14:14:28
>>
>> On Thu, 15 Jun 2023 at 16:27, Dave Page <[email protected]> wrote:
>>
>>>
>>>
>>> On Thu, 15 Jun 2023 at 11:07, Khushboo Vashi <
>>> [email protected]> wrote:
>>>
>>>> Regarding I/O analysis, what would be more beneficial, combining total
>>>> read/total write etc., per disk OR Sahil doing a different graph for each
>>>> parameter for all the disks?
>>>>
>>>
>>> I think combining R/W is fine, as long as it uses two scales in case the
>>> values are wildly different (which is likely).
>>>
>>>
>>>>
>>>> On Thu, Jun 15, 2023 at 3:22 PM Dave Page <[email protected]> wrote:
>>>>
>>>>>
>>>>>
>>>>> On Thu, 15 Jun 2023 at 09:55, Sahil Harpal <[email protected]>
>>>>> wrote:
>>>>>
>>>>>> Hi Aditya,
>>>>>>
>>>>>> Thank you for pointing this out. It would also be more convenient for
>>>>>> users to navigate to specific statistics easily.
>>>>>>
>>>>>> So, can we finalise the following design?
>>>>>> - Single dashboard with buttons to toggle between General (existing
>>>>>> graphs/stats) and System Statistics.
>>>>>>
>>>>>
>>>>> Why use buttons and not tabs? Tabs are far more flexible as they can
>>>>> be re-arranged, docked differently etc.
>>>>>
>>>>>
>>>>>> - Clubbing OS, CPU, Process, Disk and I/O in tabbed control.
>>>>>>
>>>>>
>>>>> I think the current design has too much on one big page, so yes, I'd
>>>>> want to see those split up onto different tabs. Not sure about the grouping
>>>>> though. Maybe:
>>>>>
>>>>> Summary (OS info, system specs etc)
>>>>> CPU
>>>>> Memory
>>>>> Storage (including I/O)
>>>>>
>>>>> Process info would be included on each tab as related to that tab's
>>>>> content - e.g. CPU per process on the CPU tab, memory per process on the
>>>>> memory tab, etc..)
>>>>>
>>>>>
>>>>>> Thanks,
>>>>>> Sahil
>>>>>>
>>>>>>
>>>>>> On Thu, 15 Jun 2023 at 11:21, Aditya Toshniwal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Sahil,
>>>>>>>
>>>>>>> I would suggest club OS, CPU, Process, Disk and I/O in a tabbed
>>>>>>> control. (Taking inspiration from the task manager).
>>>>>>> It will reduce the network calls, cluttering and improve DOM
>>>>>>> performance.
>>>>>>>
>>>>>>>
>>>>>>> On Thu, Jun 15, 2023 at 11:08 AM Akshay Joshi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Sahil
>>>>>>>>
>>>>>>>> On Thu, Jun 15, 2023 at 1:42 AM Sahil Harpal <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Thank you, Akshay, for your feedback.
>>>>>>>>> Here are a few more designs that I have created based on the
>>>>>>>>> discussion with my mentors. I would love to know your thoughts on them.
>>>>>>>>>
>>>>>>>>> Design 1 - Using an additional new tab for system statistics
>>>>>>>>> Design 2 - Added buttons to toggle between existing dashboard data
>>>>>>>>> and system statistics.
>>>>>>>>>
>>>>>>>>
>>>>>>>> I personally like Design 2 as we have only one main tab
>>>>>>>> "Dashboard" and then two sub-tabs "General"(Can be changed) and "System
>>>>>>>> Statistics".
>>>>>>>>
>>>>>>>>>
>>>>>>>>> Thanks,
>>>>>>>>> Sahil
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On Wed, 14 Jun 2023 at 10:12, Akshay Joshi <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi Sahil
>>>>>>>>>>
>>>>>>>>>> At first glance, it looks good to me. Seems you have created a
>>>>>>>>>> new tab System Statistics instead of using the existing dashboard.
>>>>>>>>>>
>>>>>>>>>> On Tue, Jun 13, 2023 at 4:27 PM Sahil Harpal <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Dear all,
>>>>>>>>>>>
>>>>>>>>>>> I am working on pgadmin4 to let users see their system-level
>>>>>>>>>>> statistics on the dashboard. In this mail, I've attached the wireframe to
>>>>>>>>>>> display system stats on the existing dashboard.
>>>>>>>>>>>
>>>>>>>>>>> I am open to hearing your thoughts and suggestions on the design.
>>>>>>>>>>>
>>>>>>>>>>> Thanks,
>>>>>>>>>>> Sahil
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Thanks,
>>>>>>> Aditya Toshniwal
>>>>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>>>>> <https://www.enterprisedb.com/;
>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>
>>>>>>
>>>>>
>>>>> --
>>>>> Dave Page
>>>>> Blog: https://pgsnake.blogspot.com
>>>>> Twitter: @pgsnake
>>>>>
>>>>> EDB: https://www.enterprisedb.com
>>>>>
>>>>>
>>>
>>> --
>>> Dave Page
>>> Blog: https://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EDB: https://www.enterprisedb.com
>>>
>>>
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-19 15:11 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-19 15:11 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
On Mon, 19 Jun 2023 at 14:07, Dave Page <[email protected]> wrote:
>
> Seems reasonable to me. A wireframe would seem like the best next step, to
> confirm we're all happy with what's proposed. It's hard to visualise from a
> list of bullet points.
>
Hi all,
I am attaching the updated wireframes.
Thanks,
Sahil
Attachments:
[application/pdf] System Statistics - Updated Design.pdf (1.1M, 3-System%20Statistics%20-%20Updated%20Design.pdf)
download
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-19 15:20 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Dave Page @ 2023-06-19 15:20 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
On Mon, 19 Jun 2023 at 16:11, Sahil Harpal <[email protected]>
wrote:
> On Mon, 19 Jun 2023 at 14:07, Dave Page <[email protected]> wrote:
>
>>
>> Seems reasonable to me. A wireframe would seem like the best next step,
>> to confirm we're all happy with what's proposed. It's hard to visualise
>> from a list of bullet points.
>>
>
> Hi all,
>
> I am attaching the updated wireframes.
>
Hi,
It's not quite what I was suggesting - you have Read for 4 disks on one
graph, and Write for 4 on another etc. (and then total reads and writes
separately for each disk). I was suggesting one graph per disk (as you did
for reads/writes) for each pair of read/write metrics.
I'd aim for 3 graphs per row on a normal display (Total Reads/Total Writes,
Bytes Read/Bytes Written, Time Reading/Time Writing), and 1 for small
displays (never 2, as that will always look unbalanced).
As Ashesh noted, you should also omit the "Sys" part of the names (and
various other labels will need to be cleaned up), but there's no need to do
that on the wireframe.
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-19 20:01 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-19 20:01 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
Hi Dave,
On Mon, 19 Jun 2023 at 20:51, Dave Page <[email protected]> wrote:
> I'd aim for 3 graphs per row on a normal display (Total Reads/Total
> Writes, Bytes Read/Bytes Written, Time Reading/Time Writing).
>
I am attaching the new design.
I have added 3 graphs per row for every Disk separately. Could you please
confirm whether it is what you are suggesting?
Attachments:
[application/pdf] System Statistics with updated IO analysis.pdf (1.2M, 3-System%20Statistics%20with%20updated%20IO%20analysis.pdf)
download
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-20 09:39 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Dave Page @ 2023-06-20 09:39 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
On Mon, 19 Jun 2023 at 21:01, Sahil Harpal <[email protected]>
wrote:
> Hi Dave,
>
> On Mon, 19 Jun 2023 at 20:51, Dave Page <[email protected]> wrote:
>
>> I'd aim for 3 graphs per row on a normal display (Total Reads/Total
>> Writes, Bytes Read/Bytes Written, Time Reading/Time Writing).
>>
>
> I am attaching the new design.
> I have added 3 graphs per row for every Disk separately. Could you please
> confirm whether it is what you are suggesting?
>
>
Looks good to me!
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-24 21:01 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-24 21:01 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>
I tried combining the process and handle count in a single graph. But as
you can see in the attached image, insights are not properly visible since
the difference between the values is too large. Even when the process count
is 320, without tooltips, it appears as zero. Can we draw separate graphs
for both? Or do you think the current single combined version is fine?
Attachments:
[image/png] process-handle-graph.png (16.3K, 3-process-handle-graph.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-24 21:32 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Dave Page @ 2023-06-24 21:32 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
Can you add a different scale on the right hand side? I haven’t
familiarised myself with the library we’re using now, but others I’ve used
can do that.
On Sat, 24 Jun 2023 at 22:02, Sahil Harpal <[email protected]>
wrote:
> I tried combining the process and handle count in a single graph. But as
> you can see in the attached image, insights are not properly visible since
> the difference between the values is too large. Even when the process count
> is 320, without tooltips, it appears as zero. Can we draw separate graphs
> for both? Or do you think the current single combined version is fine?
>
--
--
Dave Page
https://pgsnake.blogspot.com
EDB Postgres
https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-06-26 09:21 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-06-26 09:21 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Sun, Jun 25, 2023, 3:02 AM Dave Page <[email protected]> wrote:
> Can you add a different scale on the right hand side? I haven’t
> familiarised myself with the library we’re using now, but others I’ve used
> can do that.
>
Yeah sure. I think we can do this by passing few additional configuration
options. I'll let you know it that works.
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-11 06:28 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-11 06:28 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
Hi,
I have written code for the Summary and CPU tabs and would like to post it
here for review.
I'm currently displaying the static values in the process info pie chart
because of a minor bug. The pg_sys_process_info() query takes much longer
(around 2 mins) to execute and prevents the updation of other graphs and
tables. I tried adding it in separate useInterval with larger pollDelay,
but it didn't work. In the patch, I commented out that snippet (In
Summary.jsx).
I'm attaching the *WIP.patch* file which contains the latest changes and
also the SS of the Summary and CPU tabs.
Attachments:
[image/png] CPU.png (132.8K, 3-CPU.png)
download | view image
[image/png] Summary.png (163.1K, 4-Summary.png)
download | view image
[application/x-patch] WIP.patch (9.6K, 5-WIP.patch)
download | inline diff:
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py
index 1dac54e74..12f491ea6 100644
--- a/web/pgadmin/dashboard/__init__.py
+++ b/web/pgadmin/dashboard/__init__.py
@@ -197,6 +197,9 @@ class DashboardModule(PgAdminModule):
'dashboard.get_prepared_by_database_id',
'dashboard.config',
'dashboard.get_config_by_server_id',
+ 'dashboard.system_statistics',
+ 'dashboard.system_statistics_sid',
+ 'dashboard.system_statistics_did',
]
@@ -536,3 +539,38 @@ def terminate_session(sid=None, did=None, pid=None):
response=gettext("Success") if res else gettext("Failed"),
status=200
)
+
+
+# System Statistics Backend
[email protected]('/system_statistics',
+ endpoint='system_statistics', methods=['GET'])
[email protected]('/system_statistics/<int:sid>',
+ endpoint='system_statistics_sid', methods=['GET'])
[email protected]('/system_statistics/<int:sid>/<int:did>',
+ endpoint='system_statistics_did', methods=['GET'])
+
+@login_required
+@check_precondition
+def system_statistics(sid=None, did=None):
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_statistics.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = json.loads(
+ chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 588583eb3..d65c375ca 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -29,6 +29,8 @@ import _ from 'lodash';
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
import TabPanel from '../../../static/js/components/TabPanel';
+import Summary from './Summary';
+import CPU from './CPU';
function parseData(data) {
let res = [];
@@ -154,10 +156,14 @@ export default function Dashboard({
const [msg, setMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
const [mainTabVal, setmainTabVal] = useState(0);
- const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
+ const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
+
+ const systemStatsTabChanged = (e, tabVal) => {
+ setSystemStatsTabVal(tabVal);
+ };
if (!did) {
tabs.push(gettext('Configuration'));
@@ -171,10 +177,6 @@ export default function Dashboard({
setmainTabVal(tabVal);
};
- const systemStatsTabChanged = (e, tabVal) => {
- setSystemStatsTabVal(tabVal);
- };
-
const serverConfigColumns = [
{
accessor: 'name',
@@ -959,8 +961,8 @@ export default function Dashboard({
</TabPanel>
{/* System Statistics */}
<TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
<Tabs
value={systemStatsTabVal}
onChange={systemStatsTabChanged}
@@ -969,20 +971,32 @@ export default function Dashboard({
return <Tab key={tabValue} label={tabValue} />;
})}
</Tabs>
- </Box>
- <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
- Summary
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
- CPU
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
+ </Box>
+ <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
+ <Summary
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <CPU
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
Memory
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
Storage
- </TabPanel>
- </Box>
+ </TabPanel>
+ </Box>
</TabPanel>
</Box>
</Box>
diff --git a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
index bd465e3da..1e03cd21b 100644
--- a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
+++ b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
@@ -58,44 +58,32 @@ function tooltipPlugin(refreshRate) {
};
}
-export default function StreamingChart({xRange=75, data, options}) {
+export default function StreamingChart({xRange=75, data, options, showSecondAxis=false}) {
const chartRef = useRef();
const theme = useTheme();
const { width, height, ref:containerRef } = useResizeDetector();
- const defaultOptions = useMemo(()=>({
- title: '',
- width: width,
- height: height,
- padding: [10, 0, 10, 0],
- focus: {
- alpha: 0.3,
- },
- cursor: {
- y: false,
- drag: {
- setScale: false,
- }
- },
- series: [
+ const defaultOptions = useMemo(()=> {
+ const series = [
{},
- ...(data.datasets?.map((datum)=>({
+ ...(data.datasets?.map((datum, index) => ({
label: datum.label,
stroke: datum.borderColor,
width: options.lineBorderWidth ?? 1,
- points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius*2 }
- }))??{})
- ],
- scales: {
- x: {
- time: false,
- }
- },
- axes: [
+ scale: showSecondAxis && (index === 1) ? 'y1' : 'y',
+ points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius * 2 },
+ })) ?? []),
+ ];
+
+ const axes = [
{
show: false,
stroke: theme.palette.text.primary,
},
- {
+ ];
+
+ if(showSecondAxis){
+ axes.push({
+ scale: 'y',
grid: {
stroke: theme.otherVars.borderColor,
width: 0.5,
@@ -109,10 +97,64 @@ export default function StreamingChart({xRange=75, data, options}) {
}
return size;
}
- }
- ],
- plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
- }), [data.refreshRate, data?.datasets?.length, width, height, options]);
+ });
+ axes.push({
+ scale: 'y1',
+ side: 1,
+ stroke: theme.palette.text.primary,
+ grid: {show: false},
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ }
+ });
+ } else{
+ axes.push({
+ scale: 'y',
+ grid: {
+ stroke: theme.otherVars.borderColor,
+ width: 0.5,
+ },
+ stroke: theme.palette.text.primary,
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ }
+ });
+ }
+
+ return {
+ title: '',
+ width: width,
+ height: height,
+ padding: [10, 0, 10, 0],
+ focus: {
+ alpha: 0.3,
+ },
+ cursor: {
+ y: false,
+ drag: {
+ setScale: false,
+ }
+ },
+ series: series,
+ scales: {
+ x: {
+ time: false,
+ }
+ },
+ axes: axes,
+ plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
+ };
+ }, [data.refreshRate, data?.datasets?.length, width, height, options]);
const initialState = [
Array.from(new Array(xRange).keys()),
@@ -140,4 +182,5 @@ StreamingChart.propTypes = {
xRange: PropTypes.number.isRequired,
data: propTypeData.isRequired,
options: PropTypes.object,
+ showSecondAxis: PropTypes.bool,
};
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-11 06:34 Ashesh Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Ashesh Vashi @ 2023-07-11 06:34 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Tue, Jul 11, 2023 at 11:58 AM Sahil Harpal <[email protected]>
wrote:
> Hi,
>
> I have written code for the Summary and CPU tabs and would like to post it
> here for review.
>
> I'm currently displaying the static values in the process info pie chart
> because of a minor bug. The pg_sys_process_info() query takes much longer
> (around 2 mins) to execute and prevents the updation of other graphs and
> tables. I tried adding it in separate useInterval with larger pollDelay,
> but it didn't work. In the patch, I commented out that snippet (In
> Summary.jsx).
>
Try preloading the extension in the PostgreSQL server.
e.g. Add this extension in the 'shared_preload_libraries' in
postgresql.conf.
Reference: https://pgpedia.info/s/shared_preload_libraries.html
-- Ashesh
>
> I'm attaching the *WIP.patch* file which contains the latest changes and
> also the SS of the Summary and CPU tabs.
>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-11 08:59 Sahil Harpal <[email protected]>
parent: Ashesh Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-11 08:59 UTC (permalink / raw)
To: Ashesh Vashi <[email protected]>; +Cc: Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Tue, 11 Jul 2023 at 12:04, Ashesh Vashi <[email protected]>
wrote:
> Try preloading the extension in the PostgreSQL server.
> e.g. Add this extension in the 'shared_preload_libraries' in
> postgresql.conf.
>
> Reference: https://pgpedia.info/s/shared_preload_libraries.html
>
Thank you, Ashesh! I will try this and provide you with an update.
On Tue, 11 Jul 2023 at 12:39, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> Just to mention, make sure you're not using any chart component other than
> StreamingChart if the data is frequently updated.
> We have faced performance issues for continuously updating charts with
> Chart.js.
>
Also, as Aditya has mentioned, yeah, it will definitely affect the
performance, as re-rendering the charts would be costly. So, would love to
know suggestions for Disk info and Process info, as we have decided to use
pie and bar charts over there.
Either we can reload the charts only when the tab changes, or we can use
StreamingChart for them too?
Also, currently, StreamingChart is not formatting the axis values and takes
much space in case of larger values eg. memory_usage, handle_count etc. Can
we add a custom formatter for y-axis values? I have attached the images for
the same.
>
Attachments:
[image/png] Formatted_Axis.png (11.3K, 3-Formatted_Axis.png)
download | view image
[image/png] Current_Axis.png (12.3K, 4-Current_Axis.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-11 09:07 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-07-11 09:07 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
Hi Sahil,
On Tue, Jul 11, 2023 at 2:29 PM Sahil Harpal <[email protected]>
wrote:
> On Tue, 11 Jul 2023 at 12:04, Ashesh Vashi <[email protected]>
> wrote:
>
>> Try preloading the extension in the PostgreSQL server.
>> e.g. Add this extension in the 'shared_preload_libraries' in
>> postgresql.conf.
>>
>> Reference: https://pgpedia.info/s/shared_preload_libraries.html
>>
> Thank you, Ashesh! I will try this and provide you with an update.
>
> On Tue, 11 Jul 2023 at 12:39, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>>
>> Just to mention, make sure you're not using any chart component other
>> than StreamingChart if the data is frequently updated.
>> We have faced performance issues for continuously updating charts with
>> Chart.js.
>>
>
> Also, as Aditya has mentioned, yeah, it will definitely affect the
> performance, as re-rendering the charts would be costly. So, would love to
> know suggestions for Disk info and Process info, as we have decided to use
> pie and bar charts over there.
>
Disk info doesn't need to be updated live. You can update it once on every
tab change. I feel process info should be a bar chart. The categories are
fixed in number and we can use StreamingChart for bar chart.
> Either we can reload the charts only when the tab changes, or we can use
> StreamingChart for them too?
>
> Also, currently, StreamingChart is not formatting the axis values and
> takes much space in case of larger values eg. memory_usage, handle_count
> etc. Can we add a custom formatter for y-axis values? I have attached the
> images for the same.
>
Sure, you can tweak StreamingChart.
>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-11 15:15 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-07-11 15:15 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Tue, Jul 11, 2023 at 2:29 PM Sahil Harpal <[email protected]>
wrote:
> On Tue, 11 Jul 2023 at 12:04, Ashesh Vashi <[email protected]>
> wrote:
>
>> Try preloading the extension in the PostgreSQL server.
>> e.g. Add this extension in the 'shared_preload_libraries' in
>> postgresql.conf.
>>
>> Reference: https://pgpedia.info/s/shared_preload_libraries.html
>>
> Thank you, Ashesh! I will try this and provide you with an update.
>
I tried adding 'system_stats' in the 'shared_preload_libraries', but it
doesn't work.
On Tue, 11 Jul 2023 at 14:38, Aditya Toshniwal <
[email protected]> wrote:
> Disk info doesn't need to be updated live. You can update it once on every
> tab change. I feel process info should be a bar chart. The categories are
> fixed in number and we can use StreamingChart for bar chart.
>
Okay sure.
Please ignore the previous WIP.patch file. I missed to include few
untracked changes.
I have attached the new patch file. Sorry for the inconvenience.
New patch consist:
- Single dashboard with option to toggle between General and System
Statistics
- Summary tab (except process info details).
- CPU Details
- Memory Details
Thanks,
Sahil
Attachments:
[application/octet-stream] WIP.patch (52.6K, 3-WIP.patch)
download | inline diff:
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py
index 1dac54e74..12f491ea6 100644
--- a/web/pgadmin/dashboard/__init__.py
+++ b/web/pgadmin/dashboard/__init__.py
@@ -197,6 +197,9 @@ class DashboardModule(PgAdminModule):
'dashboard.get_prepared_by_database_id',
'dashboard.config',
'dashboard.get_config_by_server_id',
+ 'dashboard.system_statistics',
+ 'dashboard.system_statistics_sid',
+ 'dashboard.system_statistics_did',
]
@@ -536,3 +539,38 @@ def terminate_session(sid=None, did=None, pid=None):
response=gettext("Success") if res else gettext("Failed"),
status=200
)
+
+
+# System Statistics Backend
[email protected]('/system_statistics',
+ endpoint='system_statistics', methods=['GET'])
[email protected]('/system_statistics/<int:sid>',
+ endpoint='system_statistics_sid', methods=['GET'])
[email protected]('/system_statistics/<int:sid>/<int:did>',
+ endpoint='system_statistics_did', methods=['GET'])
+
+@login_required
+@check_precondition
+def system_statistics(sid=None, did=None):
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_statistics.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = json.loads(
+ chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
diff --git a/web/pgadmin/dashboard/static/js/CPU.jsx b/web/pgadmin/dashboard/static/js/CPU.jsx
new file mode 100644
index 000000000..9442f4754
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/CPU.jsx
@@ -0,0 +1,324 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getEpoch} from 'sources/utils';
+import {ChartContainer} from './Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../static/js/components/PgChart/StreamingChart';
+import {useInterval} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'cu_stats': {'User Normal': [], 'User Niced': [], 'Kernel': [], 'Idle': []},
+ 'la_stats': {'1 min': [], '5 mins': [], '10 mins': [], '15 mins': []},
+ 'pcu_stats': {},
+};
+
+export default function CPU({ sid, did, serverConencted, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+
+ const [cpuUsageInfo, cpuUsageInfoReduce] = useReducer(statsReducer, chartsDefault['cu_stats']);
+ const [loadAvgInfo, loadAvgInfoReduce] = useReducer(statsReducer, chartsDefault['la_stats']);
+ const [processCpuUsageStats, setProcessCpuUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'CPU Usage',
+ accessor: 'cpu_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const [refreshPreferences, setRefreshPreferences] = useState({'cu_stats': 5, 'la_stats': 60, 'pcu_stats': 10});
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + refreshPreferences[name];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ console.log(data);
+ setErrorMsg(null);
+ if(data.hasOwnProperty('cu_stats')){
+ let new_cu_stats = {
+ 'User Normal': data['cu_stats']['usermode_normal_process_percent']?data['cu_stats']['usermode_normal_process_percent']:0,
+ 'User Niced': data['cu_stats']['usermode_niced_process_percent']?data['cu_stats']['usermode_niced_process_percent']:0,
+ 'Kernel': data['cu_stats']['kernelmode_process_percent']?data['cu_stats']['kernelmode_process_percent']:0,
+ 'Idle': data['cu_stats']['idle_mode_percent']?data['cu_stats']['idle_mode_percent']:0,
+ };
+ cpuUsageInfoReduce({incoming: new_cu_stats});
+ }
+
+ if(data.hasOwnProperty('la_stats')){
+ let new_la_stats = {
+ '1 min': data['la_stats']['load_avg_one_minute']?data['la_stats']['load_avg_one_minute']:0,
+ '5 mins': data['la_stats']['load_avg_five_minutes']?data['la_stats']['load_avg_five_minutes']:0,
+ '10 mins': data['la_stats']['load_avg_ten_minutes']?data['la_stats']['load_avg_ten_minutes']:0,
+ '15 mins': data['la_stats']['load_avg_fifteen_minutes']?data['la_stats']['load_avg_fifteen_minutes']:0,
+ };
+ loadAvgInfoReduce({incoming: new_la_stats});
+ }
+
+ if(data.hasOwnProperty('pcu_stats')){
+ let pcu_info_list = [];
+ const pcu_info_obj = data['pcu_stats'];
+ for (const key in pcu_info_obj) {
+ pcu_info_list.push({ icon: '', pid: pcu_info_obj[key]['pid'], name: pcu_info_obj[key]['name'], cpu_usage: pcu_info_obj[key]['cpu_usage'] });
+ }
+
+ setProcessCpuUsageStats(pcu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ cpuUsageInfoReduce({reset:chartsDefault['cu_stats']});
+ loadAvgInfoReduce({reset:chartsDefault['la_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <CPUWrapper
+ cpuUsageInfo={transformData(cpuUsageInfo, refreshPreferences['cu_stats'])}
+ loadAvgInfo={transformData(loadAvgInfo, refreshPreferences['la_stats'])}
+ processCpuUsageStats={processCpuUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={true}
+ showDataPoints={false}
+ lineBorderWidth={1}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+CPU.propTypes = {
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ serverConnected: PropTypes.bool,
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function CPUWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('CPU Usage')}</div>
+ <ChartContainer id='cu-graph' title={gettext('')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.cpuUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Load Average')}</div>
+ <ChartContainer id='la-graph' title={gettext('')} datasets={props.loadAvgInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.loadAvgInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processCpuUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+CPUWrapper.propTypes = {
+ cpuUsageInfo: propTypeStats.isRequired,
+ loadAvgInfo: propTypeStats.isRequired,
+ processCpuUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 588583eb3..5e861609e 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -29,6 +29,9 @@ import _ from 'lodash';
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
import TabPanel from '../../../static/js/components/TabPanel';
+import Summary from './Summary';
+import CPU from './CPU';
+import Memory from './Memory';
function parseData(data) {
let res = [];
@@ -154,10 +157,14 @@ export default function Dashboard({
const [msg, setMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
const [mainTabVal, setmainTabVal] = useState(0);
- const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
+ const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
+
+ const systemStatsTabChanged = (e, tabVal) => {
+ setSystemStatsTabVal(tabVal);
+ };
if (!did) {
tabs.push(gettext('Configuration'));
@@ -171,10 +178,6 @@ export default function Dashboard({
setmainTabVal(tabVal);
};
- const systemStatsTabChanged = (e, tabVal) => {
- setSystemStatsTabVal(tabVal);
- };
-
const serverConfigColumns = [
{
accessor: 'name',
@@ -959,8 +962,8 @@ export default function Dashboard({
</TabPanel>
{/* System Statistics */}
<TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
<Tabs
value={systemStatsTabVal}
onChange={systemStatsTabChanged}
@@ -969,20 +972,38 @@ export default function Dashboard({
return <Tab key={tabValue} label={tabValue} />;
})}
</Tabs>
- </Box>
- <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
- Summary
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
- CPU
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
- Memory
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
- Storage
- </TabPanel>
</Box>
+ <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
+ <Summary
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <CPU
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
+ <Memory
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
+ Storage
+ </TabPanel>
+ </Box>
</TabPanel>
</Box>
</Box>
diff --git a/web/pgadmin/dashboard/static/js/Memory.jsx b/web/pgadmin/dashboard/static/js/Memory.jsx
new file mode 100644
index 000000000..82831a976
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/Memory.jsx
@@ -0,0 +1,329 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getEpoch} from 'sources/utils';
+import {ChartContainer} from './Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../static/js/components/PgChart/StreamingChart';
+import {useInterval} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'sm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'pmu_stats': {},
+};
+
+export default function Memory({ sid, did, serverConencted, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+
+ const [memoryUsageInfo, memoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['m_stats']);
+ const [swapMemoryUsageInfo, swapMemoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['sm_stats']);
+ const [processMemoryUsageStats, setProcessMemoryUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Usage',
+ accessor: 'memory_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Bytes',
+ accessor: 'memory_bytes',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const [refreshPreferences, setRefreshPreferences] = useState({'m_stats': 5, 'sm_stats': 5, 'pmu_stats': 5});
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + refreshPreferences[name];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ console.log(data);
+ setErrorMsg(null);
+ if(data.hasOwnProperty('m_stats')){
+ let new_m_stats = {
+ 'Total': data['m_stats']['total_memory']?data['m_stats']['total_memory']:0,
+ 'Used': data['m_stats']['used_memory']?data['m_stats']['used_memory']:0,
+ 'Free': data['m_stats']['free_memory']?data['m_stats']['free_memory']:0,
+ };
+ memoryUsageInfoReduce({incoming: new_m_stats});
+ }
+
+ if(data.hasOwnProperty('sm_stats')){
+ let new_sm_stats = {
+ 'Total': data['sm_stats']['swap_total']?data['sm_stats']['swap_total']:0,
+ 'Used': data['sm_stats']['swap_used']?data['sm_stats']['swap_used']:0,
+ 'Free': data['sm_stats']['swap_free']?data['sm_stats']['swap_free']:0,
+ };
+ swapMemoryUsageInfoReduce({incoming: new_sm_stats});
+ }
+
+ if(data.hasOwnProperty('pmu_stats')){
+ let pmu_info_list = [];
+ const pmu_info_obj = data['pmu_stats'];
+ for (const key in pmu_info_obj) {
+ pmu_info_list.push({ icon: '', pid: pmu_info_obj[key]['pid'], name: pmu_info_obj[key]['name'], memory_usage: pmu_info_obj[key]['memory_usage'], memory_bytes: pmu_info_obj[key]['memory_bytes'] });
+ }
+
+ setProcessMemoryUsageStats(pmu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ memoryUsageInfoReduce({reset:chartsDefault['m_stats']});
+ swapMemoryUsageInfoReduce({reset:chartsDefault['sm_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <MemoryWrapper
+ memoryUsageInfo={transformData(memoryUsageInfo, refreshPreferences['m_stats'])}
+ swapMemoryUsageInfo={transformData(swapMemoryUsageInfo, refreshPreferences['sm_stats'])}
+ processMemoryUsageStats={processMemoryUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={true}
+ showDataPoints={false}
+ lineBorderWidth={1}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Memory.propTypes = {
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ serverConnected: PropTypes.bool,
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function MemoryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Memory')}</div>
+ <ChartContainer id='m-graph' title={gettext('')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.memoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Swap Memory')}</div>
+ <ChartContainer id='sm-graph' title={gettext('')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processMemoryUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+MemoryWrapper.propTypes = {
+ memoryUsageInfo: propTypeStats.isRequired,
+ swapMemoryUsageInfo: propTypeStats.isRequired,
+ processMemoryUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/Summary.jsx b/web/pgadmin/dashboard/static/js/Summary.jsx
new file mode 100644
index 000000000..cbfcee4db
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/Summary.jsx
@@ -0,0 +1,366 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getEpoch} from 'sources/utils';
+import {ChartContainer} from './Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../static/js/components/PgChart/StreamingChart';
+import DonutChart from '../../../static/js/components/PgChart/DonutChart';
+import {useInterval} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ table: {
+ width: '100%',
+ backgroundColor: theme.otherVars.tableBg,
+ border: '1px solid rgb(221, 224, 230)',
+ },
+ tableVal: {
+ border: '1px solid rgb(221, 224, 230) !important',
+ padding: '10px !important',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'hpc_stats': {'Handle': [], 'Process': []},
+};
+
+const SummaryTable = ({ data }) => {
+ const classes = useStyles();
+ return (
+ <table className={classes.table}>
+ <thead>
+ <tr>
+ <th className={classes.tableVal}>Property</th>
+ <th className={classes.tableVal}>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {data.map((item, index) => (
+ <tr className={classes.tableVal} key={index}>
+ <td className={classes.tableVal}>{item.name}</td>
+ <td className={classes.tableVal}>{item.value}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+}
+
+export default function Summary({ sid, did, serverConencted, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+
+ const [processHandleCount, processHandleCountReduce] = useReducer(statsReducer, chartsDefault['hpc_stats']);
+ const [osStats, setOsStats] = useState([]);
+ const [cpuStats, setCpuStats] = useState([]);
+ const [processInfoStats, setProcessInfoStats] = useState({'Running': 4, 'Sleeping': 2, 'Stopped': 1, 'Zombie': 2});
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [longPollDelay, setLongPollDelay] = useState(180000);
+ const [errorMsg, setErrorMsg] = useState(null);
+
+ const tableHeader = [
+ {
+ Header: 'Property',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Value',
+ accessor: 'value',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(() => {
+ try {
+ // Fetch the latest data point from the API endpoint
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'pg_sys_os_info,pg_sys_cpu_info';
+ const api = getApiInstance();
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ let data = res.data;
+
+ const os_info_obj = data['pg_sys_os_info'];
+ let os_info_list = [
+ { icon: '', name: 'Name', value: os_info_obj['name'] },
+ { icon: '', name: 'Version', value: os_info_obj['version'] },
+ { icon: '', name: 'Host name', value: os_info_obj['host_name'] },
+ { icon: '', name: 'Domain name', value: os_info_obj['domain_name'] },
+ { icon: '', name: 'Architecture', value: os_info_obj['architecture'] },
+ { icon: '', name: 'Os up since seconds', value: os_info_obj['os_up_since_seconds'] },
+ ];
+ setOsStats(os_info_list);
+
+ const cpu_info_obj = data['pg_sys_cpu_info'];
+ let cpu_info_list = [
+ { icon: '', name: 'Vendor', value: cpu_info_obj['vendor'] },
+ { icon: '', name: 'Description', value: cpu_info_obj['description'] },
+ { icon: '', name: 'Model name', value: cpu_info_obj['model_name'] },
+ { icon: '', name: 'No of cores', value: cpu_info_obj['no_of_cores'] },
+ { icon: '', name: 'Architecture', value: cpu_info_obj['architecture'] },
+ { icon: '', name: 'Clock speed Hz', value: cpu_info_obj['clock_speed_hz'] },
+ { icon: '', name: 'L1 dcache size', value: cpu_info_obj['l1dcache_size'] },
+ { icon: '', name: 'L1 icache size', value: cpu_info_obj['l1icache_size'] },
+ { icon: '', name: 'L2 cache size', value: cpu_info_obj['l2cache_size'] },
+ { icon: '', name: 'L3 cache size', value: cpu_info_obj['l3cache_size'] },
+ ];
+ setCpuStats(cpu_info_list);
+
+ setErrorMsg(null);
+ })
+ .catch((error) => {
+ console.error('Error fetching data:', error);
+ });
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ }, [sid, did, enablePoll, pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + 5;
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ processHandleCountReduce({incoming: data['hpc_stats']});
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ processHandleCountReduce({reset:chartsDefault['hpc_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ useInterval(()=>{
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'pi_stats';
+ // axios.get(url)
+ // .then((resp)=>{
+ // let data = resp.data;
+ // console.log("pi data: ", data);
+ // })
+ // .catch((error)=>{
+ // if(!errorMsg) {
+ // if(error.response) {
+ // if (error.response.status === 428) {
+ // setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ // } else {
+ // setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ // }
+ // } else if(error.request) {
+ // setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ // return;
+ // } else {
+ // console.error(error);
+ // }
+ // }
+ // });
+ }, enablePoll ? longPollDelay : -1);
+
+ return (
+ <>
+ <SummaryWrapper
+ processHandleCount={transformData(processHandleCount, 5)}
+ osStats={osStats}
+ cpuStats={cpuStats}
+ processInfoStats={transformData(processInfoStats, 5)}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={true}
+ showDataPoints={false}
+ lineBorderWidth={1}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ </>
+ );
+}
+
+Summary.propTypes = {
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ serverConnected: PropTypes.bool,
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function SummaryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('OS Information')}</div>
+ <SummaryTable data={props.osStats} />
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Handle & Process Count')}</div>
+ <ChartContainer id='hpc-graph' title={gettext('')} datasets={props.processHandleCount.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.processHandleCount} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} showSecondAxis={true} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('CPU Information')}</div>
+ <SummaryTable data={props.cpuStats} />
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Process Information')}</div>
+ <ChartContainer id='pi-graph' title={gettext('')} datasets={props.processInfoStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <DonutChart data={props.processInfoStats.datasets} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ </>
+ );
+}
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
new file mode 100644
index 000000000..d1fced433
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
@@ -0,0 +1,87 @@
+{% set add_union = false %}
+{% if 'pg_sys_os_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_os_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_os_info()) t
+{% endif %}
+{% if add_union and 'pg_sys_cpu_info' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pg_sys_cpu_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_cpu_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_info()) t
+{% endif %}
+{% if add_union and 'hpc_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'hpc_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'hpc_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT
+ (SELECT handle_count FROM pg_sys_os_info()) AS "{{ _('Handle') }}",
+ (SELECT process_count FROM pg_sys_os_info()) AS "{{ _('Process') }}"
+ ) t
+{% endif %}
+{% if add_union and 'cu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'cu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'cu_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_usage_info()) t
+{% endif %}
+{% if add_union and 'la_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'la_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'la_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_load_avg_info()) t
+{% endif %}
+{% if add_union and 'pcu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pcu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pcu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, cpu_usage, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT total_memory, used_memory, free_memory FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'sm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'sm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'sm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT swap_total, swap_used, swap_free FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'pmu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pmu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pmu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, memory_usage, memory_bytes, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'pi_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pi_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pi_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_process_info()) t
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/DonutChart.jsx b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
new file mode 100644
index 000000000..9c917cc20
--- /dev/null
+++ b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
@@ -0,0 +1,68 @@
+import React, { useEffect, useRef } from 'react';
+import Chart from 'chart.js/auto';
+import { useResizeDetector } from 'react-resize-detector';
+
+const DonutChart = ({ data }) => {
+ const chartRef = useRef(null);
+ const chartInstance = useRef(null);
+
+ useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ if (chartInstance.current) {
+ // If chart instance exists, update the data
+ chartInstance.current.data.labels = data.map((item) => item.label);
+ chartInstance.current.data.datasets[0].data = data.map((item) => item.data);
+ chartInstance.current.update();
+ } else {
+ // If chart instance doesn't exist, create a new chart
+ const chartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: false, // Hide the labels at the top
+ },
+ },
+ animation: {
+ duration: 0, // Disable the animation
+ },
+ tooltips: {
+ callbacks: {
+ label: function (tooltipItem, chartData) {
+ const dataset = chartData.datasets[tooltipItem.datasetIndex];
+ const total = dataset.data.reduce((previousValue, currentValue) => previousValue + currentValue);
+ const currentValue = dataset.data[tooltipItem.index];
+ const percentage = ((currentValue / total) * 100).toFixed(2) + '%';
+ return dataset.label + ': ' + currentValue + ' (' + percentage + ')';
+ },
+ },
+ },
+ };
+
+ const chartData = {
+ labels: data.map((item) => item.label),
+ datasets: [
+ {
+ data: data.map((item) => item.data),
+ backgroundColor: data.map((item) => item.borderColor),
+ hoverBackgroundColor: data.map((item) => item.borderColor),
+ },
+ ],
+ };
+
+ const ctx = chartRef.current.getContext('2d');
+ chartInstance.current = new Chart(ctx, {
+ type: 'doughnut',
+ data: chartData,
+ options: chartOptions,
+ });
+ }
+ }
+ }, [data]);
+
+ return (
+ <canvas ref={chartRef} />
+ );
+};
+
+export default DonutChart;
diff --git a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
index bd465e3da..eba301f05 100644
--- a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
+++ b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
@@ -58,44 +58,89 @@ function tooltipPlugin(refreshRate) {
};
}
-export default function StreamingChart({xRange=75, data, options}) {
+export default function StreamingChart({xRange=75, data, options, showSecondAxis=false}) {
const chartRef = useRef();
const theme = useTheme();
const { width, height, ref:containerRef } = useResizeDetector();
- const defaultOptions = useMemo(()=>({
- title: '',
- width: width,
- height: height,
- padding: [10, 0, 10, 0],
- focus: {
- alpha: 0.3,
- },
- cursor: {
- y: false,
- drag: {
- setScale: false,
- }
- },
- series: [
+ const defaultOptions = useMemo(()=> {
+ const series = [
{},
- ...(data.datasets?.map((datum)=>({
+ ...(data.datasets?.map((datum, index) => ({
label: datum.label,
stroke: datum.borderColor,
width: options.lineBorderWidth ?? 1,
- points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius*2 }
- }))??{})
- ],
- scales: {
- x: {
- time: false,
- }
- },
- axes: [
+ scale: showSecondAxis && (index === 1) ? 'y1' : 'y',
+ points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius * 2 },
+ })) ?? []),
+ ];
+
+ const axes = [
{
show: false,
stroke: theme.palette.text.primary,
},
- {
+ ];
+
+ if(showSecondAxis){
+ axes.push({
+ scale: 'y',
+ grid: {
+ stroke: theme.otherVars.borderColor,
+ width: 0.5,
+ },
+ stroke: theme.palette.text.primary,
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+"";
+ }
+ const suffixes = ["", "k", "M", "B", "T"];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ axes.push({
+ scale: 'y1',
+ side: 1,
+ stroke: theme.palette.text.primary,
+ grid: {show: false},
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+"";
+ }
+ const suffixes = ["", "k", "M", "B", "T"];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ } else{
+ axes.push({
+ scale: 'y',
grid: {
stroke: theme.otherVars.borderColor,
width: 0.5,
@@ -108,11 +153,47 @@ export default function StreamingChart({xRange=75, data, options}) {
if(size < 40) size = 40;
}
return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+"";
+ }
+ const suffixes = ["", "k", "M", "B", "T"];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ }
+
+ return {
+ title: '',
+ width: width,
+ height: height,
+ padding: [10, 0, 10, 0],
+ focus: {
+ alpha: 0.3,
+ },
+ cursor: {
+ y: false,
+ drag: {
+ setScale: false,
+ }
+ },
+ series: series,
+ scales: {
+ x: {
+ time: false,
}
- }
- ],
- plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
- }), [data.refreshRate, data?.datasets?.length, width, height, options]);
+ },
+ axes: axes,
+ plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
+ };
+ }, [data.refreshRate, data?.datasets?.length, width, height, options]);
const initialState = [
Array.from(new Array(xRange).keys()),
@@ -140,4 +221,5 @@ StreamingChart.propTypes = {
xRange: PropTypes.number.isRequired,
data: propTypeData.isRequired,
options: PropTypes.object,
+ showSecondAxis: PropTypes.bool,
};
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-12 04:56 Ashesh Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Ashesh Vashi @ 2023-07-12 04:56 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Tue, Jul 11, 2023 at 8:45 PM Sahil Harpal <[email protected]>
wrote:
> On Tue, Jul 11, 2023 at 2:29 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Tue, 11 Jul 2023 at 12:04, Ashesh Vashi <[email protected]>
>> wrote:
>>
>>> Try preloading the extension in the PostgreSQL server.
>>> e.g. Add this extension in the 'shared_preload_libraries' in
>>> postgresql.conf.
>>>
>>> Reference: https://pgpedia.info/s/shared_preload_libraries.html
>>>
>> Thank you, Ashesh! I will try this and provide you with an update.
>>
> I tried adding 'system_stats' in the 'shared_preload_libraries', but it
> doesn't work.
>
Did you restart the database server?
-- Ashesh
>
> On Tue, 11 Jul 2023 at 14:38, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Disk info doesn't need to be updated live. You can update it once on
>> every tab change. I feel process info should be a bar chart. The
>> categories are fixed in number and we can use StreamingChart for bar chart.
>>
> Okay sure.
>
> Please ignore the previous WIP.patch file. I missed to include few
> untracked changes.
> I have attached the new patch file. Sorry for the inconvenience.
>
> New patch consist:
>
> - Single dashboard with option to toggle between General and System
> Statistics
> - Summary tab (except process info details).
> - CPU Details
> - Memory Details
>
>
> Thanks,
> Sahil
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-12 05:17 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-07-12 05:17 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers
On Tue, Jul 11, 2023 at 8:45 PM Sahil Harpal <[email protected]>
wrote:
> On Tue, Jul 11, 2023 at 2:29 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Tue, 11 Jul 2023 at 12:04, Ashesh Vashi <[email protected]>
>> wrote:
>>
>>> Try preloading the extension in the PostgreSQL server.
>>> e.g. Add this extension in the 'shared_preload_libraries' in
>>> postgresql.conf.
>>>
>>> Reference: https://pgpedia.info/s/shared_preload_libraries.html
>>>
>> Thank you, Ashesh! I will try this and provide you with an update.
>>
> I tried adding 'system_stats' in the 'shared_preload_libraries', but it
> doesn't work.
>
> On Tue, 11 Jul 2023 at 14:38, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Disk info doesn't need to be updated live. You can update it once on
>> every tab change. I feel process info should be a bar chart. The
>> categories are fixed in number and we can use StreamingChart for bar chart.
>>
> Okay sure.
>
> Please ignore the previous WIP.patch file. I missed to include few
> untracked changes.
> I have attached the new patch file. Sorry for the inconvenience.
>
> New patch consist:
>
> - Single dashboard with option to toggle between General and System
> Statistics
> - Summary tab (except process info details).
> - CPU Details
> - Memory Details
>
>
> It fails while applying. Can you please rebase your patch and send it?
Thanks,
> Sahil
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-12 10:28 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-07-12 10:28 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers
On Wed, 12 Jul 2023 at 10:47, Khushboo Vashi <
[email protected]> wrote:
> It fails while applying. Can you please rebase your patch and send it?
>
Could you please try the attached patch.
Attachments:
[application/octet-stream] WIP.patch (62.1K, 3-WIP.patch)
download | inline diff:
From b1b5f8a2e3810d718f74f1a475c61ef07593dde0 Mon Sep 17 00:00:00 2001
From: unknown <[email protected]>
Date: Wed, 21 Jun 2023 13:05:08 +0530
Subject: [PATCH] Added dashboard base layout
---
web/pgadmin/dashboard/static/js/Dashboard.jsx | 175 ++++++++++++------
1 file changed, 116 insertions(+), 59 deletions(-)
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 7194fcc10..588583eb3 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -148,9 +148,13 @@ export default function Dashboard({
}) {
const classes = useStyles();
let tabs = [gettext('Sessions'), gettext('Locks'), gettext('Prepared Transactions')];
+ let mainTabs = [gettext('General'), gettext('System Statistics')];
+ let systemStatsTabs = [gettext('Summary'), gettext('CPU'), gettext('Memory'), gettext('Storage')];
const [dashData, setdashData] = useState([]);
const [msg, setMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
+ const [mainTabVal, setmainTabVal] = useState(0);
+ const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
@@ -163,6 +167,14 @@ export default function Dashboard({
setTabVal(tabVal);
};
+ const mainTabChanged = (e, tabVal) => {
+ setmainTabVal(tabVal);
+ };
+
+ const systemStatsTabChanged = (e, tabVal) => {
+ setSystemStatsTabVal(tabVal);
+ };
+
const serverConfigColumns = [
{
accessor: 'name',
@@ -867,68 +879,113 @@ export default function Dashboard({
{sid && props.serverConnected ? (
<Box className={classes.dashboardPanel}>
<Box className={classes.emptyPanel}>
- {!_.isUndefined(preferences) && preferences.show_graphs && (
- <Graphs
- key={sid + did}
- preferences={preferences}
- sid={sid}
- did={did}
- pageVisible={props.panelVisible}
- ></Graphs>
- )}
- {!_.isUndefined(preferences) && preferences.show_activity && (
- <Box className={classes.panelContent}>
- <Box
- className={classes.cardHeader}
- title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
- >
- {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ <Box className={classes.panelContent}>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={mainTabVal}
+ onChange={mainTabChanged}
+ >
+ {mainTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
</Box>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
- <Tabs
- value={tabVal}
- onChange={tabChanged}
- >
- {tabs.map((tabValue) => {
- return <Tab key={tabValue} label={tabValue} />;
- })}
- <RefreshButton/>
- </Tabs>
+ {/* General Statistics */}
+ <TabPanel value={mainTabVal} index={0} classNameRoot={classes.tabPanel}>
+ {!_.isUndefined(preferences) && preferences.show_graphs && (
+ <Graphs
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ ></Graphs>
+ )}
+ {!_.isUndefined(preferences) && preferences.show_activity && (
+ <Box className={classes.panelContent}>
+ <Box
+ className={classes.cardHeader}
+ title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
+ >
+ {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ </Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={tabVal}
+ onChange={tabChanged}
+ >
+ {tabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
+ </Box>
+ <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ CustomHeader={CustomActiveOnlyHeader}
+ columns={activityColumns}
+ data={filteredDashData}
+ schema={schemaDict}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databaseLocksColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databasePreparedColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={serverConfigColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ </Box>
+ </Box>
+ )}
+ </TabPanel>
+ {/* System Statistics */}
+ <TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={systemStatsTabVal}
+ onChange={systemStatsTabChanged}
+ >
+ {systemStatsTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ </Tabs>
+ </Box>
+ <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
+ Summary
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
+ CPU
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
+ Memory
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
+ Storage
+ </TabPanel>
</Box>
- <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- CustomHeader={CustomActiveOnlyHeader}
- columns={activityColumns}
- data={filteredDashData}
- schema={schemaDict}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databaseLocksColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databasePreparedColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={serverConfigColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- </Box>
+ </TabPanel>
</Box>
- )}
+ </Box>
</Box>
</Box>
) : showDefaultContents() }
--
2.41.0.windows.1
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py
index 1dac54e74..12f491ea6 100644
--- a/web/pgadmin/dashboard/__init__.py
+++ b/web/pgadmin/dashboard/__init__.py
@@ -197,6 +197,9 @@ class DashboardModule(PgAdminModule):
'dashboard.get_prepared_by_database_id',
'dashboard.config',
'dashboard.get_config_by_server_id',
+ 'dashboard.system_statistics',
+ 'dashboard.system_statistics_sid',
+ 'dashboard.system_statistics_did',
]
@@ -536,3 +539,38 @@ def terminate_session(sid=None, did=None, pid=None):
response=gettext("Success") if res else gettext("Failed"),
status=200
)
+
+
+# System Statistics Backend
[email protected]('/system_statistics',
+ endpoint='system_statistics', methods=['GET'])
[email protected]('/system_statistics/<int:sid>',
+ endpoint='system_statistics_sid', methods=['GET'])
[email protected]('/system_statistics/<int:sid>/<int:did>',
+ endpoint='system_statistics_did', methods=['GET'])
+
+@login_required
+@check_precondition
+def system_statistics(sid=None, did=None):
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_statistics.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = json.loads(
+ chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
diff --git a/web/pgadmin/dashboard/static/js/CPU.jsx b/web/pgadmin/dashboard/static/js/CPU.jsx
new file mode 100644
index 000000000..9442f4754
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/CPU.jsx
@@ -0,0 +1,324 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getEpoch} from 'sources/utils';
+import {ChartContainer} from './Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../static/js/components/PgChart/StreamingChart';
+import {useInterval} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'cu_stats': {'User Normal': [], 'User Niced': [], 'Kernel': [], 'Idle': []},
+ 'la_stats': {'1 min': [], '5 mins': [], '10 mins': [], '15 mins': []},
+ 'pcu_stats': {},
+};
+
+export default function CPU({ sid, did, serverConencted, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+
+ const [cpuUsageInfo, cpuUsageInfoReduce] = useReducer(statsReducer, chartsDefault['cu_stats']);
+ const [loadAvgInfo, loadAvgInfoReduce] = useReducer(statsReducer, chartsDefault['la_stats']);
+ const [processCpuUsageStats, setProcessCpuUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'CPU Usage',
+ accessor: 'cpu_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const [refreshPreferences, setRefreshPreferences] = useState({'cu_stats': 5, 'la_stats': 60, 'pcu_stats': 10});
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + refreshPreferences[name];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ console.log(data);
+ setErrorMsg(null);
+ if(data.hasOwnProperty('cu_stats')){
+ let new_cu_stats = {
+ 'User Normal': data['cu_stats']['usermode_normal_process_percent']?data['cu_stats']['usermode_normal_process_percent']:0,
+ 'User Niced': data['cu_stats']['usermode_niced_process_percent']?data['cu_stats']['usermode_niced_process_percent']:0,
+ 'Kernel': data['cu_stats']['kernelmode_process_percent']?data['cu_stats']['kernelmode_process_percent']:0,
+ 'Idle': data['cu_stats']['idle_mode_percent']?data['cu_stats']['idle_mode_percent']:0,
+ };
+ cpuUsageInfoReduce({incoming: new_cu_stats});
+ }
+
+ if(data.hasOwnProperty('la_stats')){
+ let new_la_stats = {
+ '1 min': data['la_stats']['load_avg_one_minute']?data['la_stats']['load_avg_one_minute']:0,
+ '5 mins': data['la_stats']['load_avg_five_minutes']?data['la_stats']['load_avg_five_minutes']:0,
+ '10 mins': data['la_stats']['load_avg_ten_minutes']?data['la_stats']['load_avg_ten_minutes']:0,
+ '15 mins': data['la_stats']['load_avg_fifteen_minutes']?data['la_stats']['load_avg_fifteen_minutes']:0,
+ };
+ loadAvgInfoReduce({incoming: new_la_stats});
+ }
+
+ if(data.hasOwnProperty('pcu_stats')){
+ let pcu_info_list = [];
+ const pcu_info_obj = data['pcu_stats'];
+ for (const key in pcu_info_obj) {
+ pcu_info_list.push({ icon: '', pid: pcu_info_obj[key]['pid'], name: pcu_info_obj[key]['name'], cpu_usage: pcu_info_obj[key]['cpu_usage'] });
+ }
+
+ setProcessCpuUsageStats(pcu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ cpuUsageInfoReduce({reset:chartsDefault['cu_stats']});
+ loadAvgInfoReduce({reset:chartsDefault['la_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <CPUWrapper
+ cpuUsageInfo={transformData(cpuUsageInfo, refreshPreferences['cu_stats'])}
+ loadAvgInfo={transformData(loadAvgInfo, refreshPreferences['la_stats'])}
+ processCpuUsageStats={processCpuUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={true}
+ showDataPoints={false}
+ lineBorderWidth={1}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+CPU.propTypes = {
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ serverConnected: PropTypes.bool,
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function CPUWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('CPU Usage')}</div>
+ <ChartContainer id='cu-graph' title={gettext('')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.cpuUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Load Average')}</div>
+ <ChartContainer id='la-graph' title={gettext('')} datasets={props.loadAvgInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.loadAvgInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processCpuUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+CPUWrapper.propTypes = {
+ cpuUsageInfo: propTypeStats.isRequired,
+ loadAvgInfo: propTypeStats.isRequired,
+ processCpuUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 588583eb3..5e861609e 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -29,6 +29,9 @@ import _ from 'lodash';
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
import TabPanel from '../../../static/js/components/TabPanel';
+import Summary from './Summary';
+import CPU from './CPU';
+import Memory from './Memory';
function parseData(data) {
let res = [];
@@ -154,10 +157,14 @@ export default function Dashboard({
const [msg, setMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
const [mainTabVal, setmainTabVal] = useState(0);
- const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
+ const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
+
+ const systemStatsTabChanged = (e, tabVal) => {
+ setSystemStatsTabVal(tabVal);
+ };
if (!did) {
tabs.push(gettext('Configuration'));
@@ -171,10 +178,6 @@ export default function Dashboard({
setmainTabVal(tabVal);
};
- const systemStatsTabChanged = (e, tabVal) => {
- setSystemStatsTabVal(tabVal);
- };
-
const serverConfigColumns = [
{
accessor: 'name',
@@ -959,8 +962,8 @@ export default function Dashboard({
</TabPanel>
{/* System Statistics */}
<TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
<Tabs
value={systemStatsTabVal}
onChange={systemStatsTabChanged}
@@ -969,20 +972,38 @@ export default function Dashboard({
return <Tab key={tabValue} label={tabValue} />;
})}
</Tabs>
- </Box>
- <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
- Summary
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
- CPU
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
- Memory
- </TabPanel>
- <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
- Storage
- </TabPanel>
</Box>
+ <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
+ <Summary
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <CPU
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
+ <Memory
+ key={sid + did}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
+ Storage
+ </TabPanel>
+ </Box>
</TabPanel>
</Box>
</Box>
diff --git a/web/pgadmin/dashboard/static/js/Memory.jsx b/web/pgadmin/dashboard/static/js/Memory.jsx
new file mode 100644
index 000000000..82831a976
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/Memory.jsx
@@ -0,0 +1,329 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getEpoch} from 'sources/utils';
+import {ChartContainer} from './Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../static/js/components/PgChart/StreamingChart';
+import {useInterval} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'sm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'pmu_stats': {},
+};
+
+export default function Memory({ sid, did, serverConencted, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+
+ const [memoryUsageInfo, memoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['m_stats']);
+ const [swapMemoryUsageInfo, swapMemoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['sm_stats']);
+ const [processMemoryUsageStats, setProcessMemoryUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Usage',
+ accessor: 'memory_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Bytes',
+ accessor: 'memory_bytes',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const [refreshPreferences, setRefreshPreferences] = useState({'m_stats': 5, 'sm_stats': 5, 'pmu_stats': 5});
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + refreshPreferences[name];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ console.log(data);
+ setErrorMsg(null);
+ if(data.hasOwnProperty('m_stats')){
+ let new_m_stats = {
+ 'Total': data['m_stats']['total_memory']?data['m_stats']['total_memory']:0,
+ 'Used': data['m_stats']['used_memory']?data['m_stats']['used_memory']:0,
+ 'Free': data['m_stats']['free_memory']?data['m_stats']['free_memory']:0,
+ };
+ memoryUsageInfoReduce({incoming: new_m_stats});
+ }
+
+ if(data.hasOwnProperty('sm_stats')){
+ let new_sm_stats = {
+ 'Total': data['sm_stats']['swap_total']?data['sm_stats']['swap_total']:0,
+ 'Used': data['sm_stats']['swap_used']?data['sm_stats']['swap_used']:0,
+ 'Free': data['sm_stats']['swap_free']?data['sm_stats']['swap_free']:0,
+ };
+ swapMemoryUsageInfoReduce({incoming: new_sm_stats});
+ }
+
+ if(data.hasOwnProperty('pmu_stats')){
+ let pmu_info_list = [];
+ const pmu_info_obj = data['pmu_stats'];
+ for (const key in pmu_info_obj) {
+ pmu_info_list.push({ icon: '', pid: pmu_info_obj[key]['pid'], name: pmu_info_obj[key]['name'], memory_usage: pmu_info_obj[key]['memory_usage'], memory_bytes: pmu_info_obj[key]['memory_bytes'] });
+ }
+
+ setProcessMemoryUsageStats(pmu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ memoryUsageInfoReduce({reset:chartsDefault['m_stats']});
+ swapMemoryUsageInfoReduce({reset:chartsDefault['sm_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <MemoryWrapper
+ memoryUsageInfo={transformData(memoryUsageInfo, refreshPreferences['m_stats'])}
+ swapMemoryUsageInfo={transformData(swapMemoryUsageInfo, refreshPreferences['sm_stats'])}
+ processMemoryUsageStats={processMemoryUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={true}
+ showDataPoints={false}
+ lineBorderWidth={1}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Memory.propTypes = {
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ serverConnected: PropTypes.bool,
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function MemoryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Memory')}</div>
+ <ChartContainer id='m-graph' title={gettext('')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.memoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Swap Memory')}</div>
+ <ChartContainer id='sm-graph' title={gettext('')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processMemoryUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+MemoryWrapper.propTypes = {
+ memoryUsageInfo: propTypeStats.isRequired,
+ swapMemoryUsageInfo: propTypeStats.isRequired,
+ processMemoryUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/Summary.jsx b/web/pgadmin/dashboard/static/js/Summary.jsx
new file mode 100644
index 000000000..cbfcee4db
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/Summary.jsx
@@ -0,0 +1,366 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getEpoch} from 'sources/utils';
+import {ChartContainer} from './Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../static/js/components/PgChart/StreamingChart';
+import DonutChart from '../../../static/js/components/PgChart/DonutChart';
+import {useInterval} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ table: {
+ width: '100%',
+ backgroundColor: theme.otherVars.tableBg,
+ border: '1px solid rgb(221, 224, 230)',
+ },
+ tableVal: {
+ border: '1px solid rgb(221, 224, 230) !important',
+ padding: '10px !important',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'hpc_stats': {'Handle': [], 'Process': []},
+};
+
+const SummaryTable = ({ data }) => {
+ const classes = useStyles();
+ return (
+ <table className={classes.table}>
+ <thead>
+ <tr>
+ <th className={classes.tableVal}>Property</th>
+ <th className={classes.tableVal}>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {data.map((item, index) => (
+ <tr className={classes.tableVal} key={index}>
+ <td className={classes.tableVal}>{item.name}</td>
+ <td className={classes.tableVal}>{item.value}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+}
+
+export default function Summary({ sid, did, serverConencted, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+
+ const [processHandleCount, processHandleCountReduce] = useReducer(statsReducer, chartsDefault['hpc_stats']);
+ const [osStats, setOsStats] = useState([]);
+ const [cpuStats, setCpuStats] = useState([]);
+ const [processInfoStats, setProcessInfoStats] = useState({'Running': 4, 'Sleeping': 2, 'Stopped': 1, 'Zombie': 2});
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [longPollDelay, setLongPollDelay] = useState(180000);
+ const [errorMsg, setErrorMsg] = useState(null);
+
+ const tableHeader = [
+ {
+ Header: 'Property',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Value',
+ accessor: 'value',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(() => {
+ try {
+ // Fetch the latest data point from the API endpoint
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'pg_sys_os_info,pg_sys_cpu_info';
+ const api = getApiInstance();
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ let data = res.data;
+
+ const os_info_obj = data['pg_sys_os_info'];
+ let os_info_list = [
+ { icon: '', name: 'Name', value: os_info_obj['name'] },
+ { icon: '', name: 'Version', value: os_info_obj['version'] },
+ { icon: '', name: 'Host name', value: os_info_obj['host_name'] },
+ { icon: '', name: 'Domain name', value: os_info_obj['domain_name'] },
+ { icon: '', name: 'Architecture', value: os_info_obj['architecture'] },
+ { icon: '', name: 'Os up since seconds', value: os_info_obj['os_up_since_seconds'] },
+ ];
+ setOsStats(os_info_list);
+
+ const cpu_info_obj = data['pg_sys_cpu_info'];
+ let cpu_info_list = [
+ { icon: '', name: 'Vendor', value: cpu_info_obj['vendor'] },
+ { icon: '', name: 'Description', value: cpu_info_obj['description'] },
+ { icon: '', name: 'Model name', value: cpu_info_obj['model_name'] },
+ { icon: '', name: 'No of cores', value: cpu_info_obj['no_of_cores'] },
+ { icon: '', name: 'Architecture', value: cpu_info_obj['architecture'] },
+ { icon: '', name: 'Clock speed Hz', value: cpu_info_obj['clock_speed_hz'] },
+ { icon: '', name: 'L1 dcache size', value: cpu_info_obj['l1dcache_size'] },
+ { icon: '', name: 'L1 icache size', value: cpu_info_obj['l1icache_size'] },
+ { icon: '', name: 'L2 cache size', value: cpu_info_obj['l2cache_size'] },
+ { icon: '', name: 'L3 cache size', value: cpu_info_obj['l3cache_size'] },
+ ];
+ setCpuStats(cpu_info_list);
+
+ setErrorMsg(null);
+ })
+ .catch((error) => {
+ console.error('Error fetching data:', error);
+ });
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ }, [sid, did, enablePoll, pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + 5;
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ processHandleCountReduce({incoming: data['hpc_stats']});
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ processHandleCountReduce({reset:chartsDefault['hpc_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ useInterval(()=>{
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'pi_stats';
+ // axios.get(url)
+ // .then((resp)=>{
+ // let data = resp.data;
+ // console.log("pi data: ", data);
+ // })
+ // .catch((error)=>{
+ // if(!errorMsg) {
+ // if(error.response) {
+ // if (error.response.status === 428) {
+ // setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ // } else {
+ // setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ // }
+ // } else if(error.request) {
+ // setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ // return;
+ // } else {
+ // console.error(error);
+ // }
+ // }
+ // });
+ }, enablePoll ? longPollDelay : -1);
+
+ return (
+ <>
+ <SummaryWrapper
+ processHandleCount={transformData(processHandleCount, 5)}
+ osStats={osStats}
+ cpuStats={cpuStats}
+ processInfoStats={transformData(processInfoStats, 5)}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={true}
+ showDataPoints={false}
+ lineBorderWidth={1}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ </>
+ );
+}
+
+Summary.propTypes = {
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ serverConnected: PropTypes.bool,
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function SummaryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('OS Information')}</div>
+ <SummaryTable data={props.osStats} />
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Handle & Process Count')}</div>
+ <ChartContainer id='hpc-graph' title={gettext('')} datasets={props.processHandleCount.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.processHandleCount} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} showSecondAxis={true} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('CPU Information')}</div>
+ <SummaryTable data={props.cpuStats} />
+ </Grid>
+ <Grid item md={6}>
+ <div className={classes.containerHeader}>{gettext('Process Information')}</div>
+ <ChartContainer id='pi-graph' title={gettext('')} datasets={props.processInfoStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <DonutChart data={props.processInfoStats.datasets} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ </>
+ );
+}
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
new file mode 100644
index 000000000..d1fced433
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
@@ -0,0 +1,87 @@
+{% set add_union = false %}
+{% if 'pg_sys_os_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_os_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_os_info()) t
+{% endif %}
+{% if add_union and 'pg_sys_cpu_info' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pg_sys_cpu_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_cpu_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_info()) t
+{% endif %}
+{% if add_union and 'hpc_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'hpc_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'hpc_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT
+ (SELECT handle_count FROM pg_sys_os_info()) AS "{{ _('Handle') }}",
+ (SELECT process_count FROM pg_sys_os_info()) AS "{{ _('Process') }}"
+ ) t
+{% endif %}
+{% if add_union and 'cu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'cu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'cu_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_usage_info()) t
+{% endif %}
+{% if add_union and 'la_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'la_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'la_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_load_avg_info()) t
+{% endif %}
+{% if add_union and 'pcu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pcu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pcu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, cpu_usage, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT total_memory, used_memory, free_memory FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'sm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'sm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'sm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT swap_total, swap_used, swap_free FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'pmu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pmu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pmu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, memory_usage, memory_bytes, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'pi_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pi_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pi_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_process_info()) t
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/DonutChart.jsx b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
new file mode 100644
index 000000000..9c917cc20
--- /dev/null
+++ b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
@@ -0,0 +1,68 @@
+import React, { useEffect, useRef } from 'react';
+import Chart from 'chart.js/auto';
+import { useResizeDetector } from 'react-resize-detector';
+
+const DonutChart = ({ data }) => {
+ const chartRef = useRef(null);
+ const chartInstance = useRef(null);
+
+ useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ if (chartInstance.current) {
+ // If chart instance exists, update the data
+ chartInstance.current.data.labels = data.map((item) => item.label);
+ chartInstance.current.data.datasets[0].data = data.map((item) => item.data);
+ chartInstance.current.update();
+ } else {
+ // If chart instance doesn't exist, create a new chart
+ const chartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: false, // Hide the labels at the top
+ },
+ },
+ animation: {
+ duration: 0, // Disable the animation
+ },
+ tooltips: {
+ callbacks: {
+ label: function (tooltipItem, chartData) {
+ const dataset = chartData.datasets[tooltipItem.datasetIndex];
+ const total = dataset.data.reduce((previousValue, currentValue) => previousValue + currentValue);
+ const currentValue = dataset.data[tooltipItem.index];
+ const percentage = ((currentValue / total) * 100).toFixed(2) + '%';
+ return dataset.label + ': ' + currentValue + ' (' + percentage + ')';
+ },
+ },
+ },
+ };
+
+ const chartData = {
+ labels: data.map((item) => item.label),
+ datasets: [
+ {
+ data: data.map((item) => item.data),
+ backgroundColor: data.map((item) => item.borderColor),
+ hoverBackgroundColor: data.map((item) => item.borderColor),
+ },
+ ],
+ };
+
+ const ctx = chartRef.current.getContext('2d');
+ chartInstance.current = new Chart(ctx, {
+ type: 'doughnut',
+ data: chartData,
+ options: chartOptions,
+ });
+ }
+ }
+ }, [data]);
+
+ return (
+ <canvas ref={chartRef} />
+ );
+};
+
+export default DonutChart;
diff --git a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
index bd465e3da..eba301f05 100644
--- a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
+++ b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
@@ -58,44 +58,89 @@ function tooltipPlugin(refreshRate) {
};
}
-export default function StreamingChart({xRange=75, data, options}) {
+export default function StreamingChart({xRange=75, data, options, showSecondAxis=false}) {
const chartRef = useRef();
const theme = useTheme();
const { width, height, ref:containerRef } = useResizeDetector();
- const defaultOptions = useMemo(()=>({
- title: '',
- width: width,
- height: height,
- padding: [10, 0, 10, 0],
- focus: {
- alpha: 0.3,
- },
- cursor: {
- y: false,
- drag: {
- setScale: false,
- }
- },
- series: [
+ const defaultOptions = useMemo(()=> {
+ const series = [
{},
- ...(data.datasets?.map((datum)=>({
+ ...(data.datasets?.map((datum, index) => ({
label: datum.label,
stroke: datum.borderColor,
width: options.lineBorderWidth ?? 1,
- points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius*2 }
- }))??{})
- ],
- scales: {
- x: {
- time: false,
- }
- },
- axes: [
+ scale: showSecondAxis && (index === 1) ? 'y1' : 'y',
+ points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius * 2 },
+ })) ?? []),
+ ];
+
+ const axes = [
{
show: false,
stroke: theme.palette.text.primary,
},
- {
+ ];
+
+ if(showSecondAxis){
+ axes.push({
+ scale: 'y',
+ grid: {
+ stroke: theme.otherVars.borderColor,
+ width: 0.5,
+ },
+ stroke: theme.palette.text.primary,
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+"";
+ }
+ const suffixes = ["", "k", "M", "B", "T"];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ axes.push({
+ scale: 'y1',
+ side: 1,
+ stroke: theme.palette.text.primary,
+ grid: {show: false},
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+"";
+ }
+ const suffixes = ["", "k", "M", "B", "T"];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ } else{
+ axes.push({
+ scale: 'y',
grid: {
stroke: theme.otherVars.borderColor,
width: 0.5,
@@ -108,11 +153,47 @@ export default function StreamingChart({xRange=75, data, options}) {
if(size < 40) size = 40;
}
return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+"";
+ }
+ const suffixes = ["", "k", "M", "B", "T"];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ }
+
+ return {
+ title: '',
+ width: width,
+ height: height,
+ padding: [10, 0, 10, 0],
+ focus: {
+ alpha: 0.3,
+ },
+ cursor: {
+ y: false,
+ drag: {
+ setScale: false,
+ }
+ },
+ series: series,
+ scales: {
+ x: {
+ time: false,
}
- }
- ],
- plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
- }), [data.refreshRate, data?.datasets?.length, width, height, options]);
+ },
+ axes: axes,
+ plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
+ };
+ }, [data.refreshRate, data?.datasets?.length, width, height, options]);
const initialState = [
Array.from(new Array(xRange).keys()),
@@ -140,4 +221,5 @@ StreamingChart.propTypes = {
xRange: PropTypes.number.isRequired,
data: propTypeData.isRequired,
options: PropTypes.object,
+ showSecondAxis: PropTypes.bool,
};
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-12 10:49 Sahil Harpal <[email protected]>
parent: Ashesh Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-12 10:49 UTC (permalink / raw)
To: Ashesh Vashi <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Wed, 12 Jul 2023 at 10:26, Ashesh Vashi <[email protected]>
wrote:
> On Tue, Jul 11, 2023 at 8:45 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Tue, Jul 11, 2023 at 2:29 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> On Tue, 11 Jul 2023 at 12:04, Ashesh Vashi <
>>> [email protected]> wrote:
>>>
>>>> Try preloading the extension in the PostgreSQL server.
>>>> e.g. Add this extension in the 'shared_preload_libraries' in
>>>> postgresql.conf.
>>>>
>>>> Reference: https://pgpedia.info/s/shared_preload_libraries.html
>>>>
>>> Thank you, Ashesh! I will try this and provide you with an update.
>>>
>> I tried adding 'system_stats' in the 'shared_preload_libraries', but it
>> doesn't work.
>>
> Did you restart the database server?
>
Yep.
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-12 11:03 Ashesh Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Ashesh Vashi @ 2023-07-12 11:03 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Wed, Jul 12, 2023 at 4:20 PM Sahil Harpal <[email protected]>
wrote:
> On Wed, 12 Jul 2023 at 10:26, Ashesh Vashi <[email protected]>
> wrote:
>
>> On Tue, Jul 11, 2023 at 8:45 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> On Tue, Jul 11, 2023 at 2:29 PM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> On Tue, 11 Jul 2023 at 12:04, Ashesh Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>> Try preloading the extension in the PostgreSQL server.
>>>>> e.g. Add this extension in the 'shared_preload_libraries' in
>>>>> postgresql.conf.
>>>>>
>>>>> Reference: https://pgpedia.info/s/shared_preload_libraries.html
>>>>>
>>>> Thank you, Ashesh! I will try this and provide you with an update.
>>>>
>>> I tried adding 'system_stats' in the 'shared_preload_libraries', but it
>>> doesn't work.
>>>
>> Did you restart the database server?
>>
> Yep.
>
If it is an extension issue, you may want to create an issue in its github
repo with reproduction steps, current & expected behaviour.
-- Ashesh
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-20 20:29 Sahil Harpal <[email protected]>
parent: Ashesh Vashi <[email protected]>
0 siblings, 0 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-07-20 20:29 UTC (permalink / raw)
To: Ashesh Vashi <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Khushboo Vashi <[email protected]>; pgadmin-hackers
On Wed, Jul 12, 2023, 4:33 PM Ashesh Vashi <[email protected]>
wrote:
> If it is an extension issue, you may want to create an issue in its github
> repo with reproduction steps, current & expected behaviour.
>
I think the issue is mostly with windows systems can anyone please confirm
whether the pg_sys_process_info() take long time to execute on Linux or Mac
systems also? Like in case of Windows it call Win32_Process.
If we somehow we able to execute this query seperately, I think we would
probably resolve this issue as then it won't block others.
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-20 20:32 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-20 20:32 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; pgadmin-hackers
On Wed, Jul 12, 2023, 3:58 PM Sahil Harpal <[email protected]>
wrote:
>
> Could you please try the attached patch.
>
Could you please confirm whether it's working or not? Also is there
anything that we can improve in this patch.
Thanks,
Sahil
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-23 20:00 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-23 20:00 UTC (permalink / raw)
To: pgadmin-hackers; +Cc: Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hello all,
Regarding the storage tab and the disk information charts, we have decided
to display two charts:
1. A pie chart to represent the proportion of total space.
2. A bar chart to display the space available and used.
However, I'm facing an issue with the output of a query, as it is returning
null values for "mount_point" and "drive_letter," and the same names for
"file_system." This makes it difficult to distinguish between different
disks.
We need to find a suitable label or identifier to distinguish all the disks
correctly. Any suggestions or solutions to this problem?
Please find the attached image showing the output of a query for your
reference.
Thanks,
Sahil
>
Attachments:
[image/png] pg_sys_disk_info.png (63.9K, 3-pg_sys_disk_info.png)
download | view image
[image/png] pg_sys_disk_info__design.png (91.5K, 4-pg_sys_disk_info__design.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-24 05:26 Ashesh Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Ashesh Vashi @ 2023-07-24 05:26 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
On Mon, Jul 24, 2023 at 1:30 AM Sahil Harpal <[email protected]>
wrote:
> Hello all,
> Regarding the storage tab and the disk information charts, we have decided
> to display two charts:
>
> 1. A pie chart to represent the proportion of total space.
> 2. A bar chart to display the space available and used.
>
> However, I'm facing an issue with the output of a query, as it is
> returning null values for "mount_point" and "drive_letter," and the same
> names for "file_system." This makes it difficult to distinguish between
> different disks.
>
Please share the exact output of the query to get any suggestions.
-- Ashesh
>
> We need to find a suitable label or identifier to distinguish all the
> disks correctly. Any suggestions or solutions to this problem?
>
> Please find the attached image showing the output of a query for your
> reference.
>
> Thanks,
> Sahil
>
>>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-24 09:29 Sahil Harpal <[email protected]>
parent: Ashesh Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-24 09:29 UTC (permalink / raw)
To: Ashesh Vashi <[email protected]>; +Cc: pgadmin-hackers; Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
On Mon, 24 Jul 2023 at 10:57, Ashesh Vashi <[email protected]>
wrote:
> Please share the exact output of the query to get any suggestions.
>
The following is the output returned by the query:
mount_point | file_system | drive_letter | drive_type | file_system_type |
> total_space | used_space | free_space | total_inodes | used_inodes |
> free_inodes
> -------------+-------------+--------------+------------+------------------+--------------+--------------+--------------+--------------+-------------+-------------
>
> | NTFS | C: | 3 |
> | 499350761472 | 86123380736 | 413227380736 | | |
> | NTFS | | 3 |
> | 633335808 | 543641600 | 89694208 | | |
> | NTFS | | 3 |
> | 500913139712 | 106947141632 | 393965998080 | | |
> | NTFS | | 3 |
> | 499288895488 | 237867192320 | 261421703168 | | |
> | FAT32 | | 3 |
> | 100663296 | 52180992 | 48482304 | | |
> (5 rows)
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-24 10:04 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Dave Page @ 2023-07-24 10:04 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>
On Mon, 24 Jul 2023 at 10:30, Sahil Harpal <[email protected]>
wrote:
> On Mon, 24 Jul 2023 at 10:57, Ashesh Vashi <[email protected]>
> wrote:
>
>> Please share the exact output of the query to get any suggestions.
>>
> The following is the output returned by the query:
>
> mount_point | file_system | drive_letter | drive_type | file_system_type
>> | total_space | used_space | free_space | total_inodes | used_inodes |
>> free_inodes
>> -------------+-------------+--------------+------------+------------------+--------------+--------------+--------------+--------------+-------------+-------------
>>
>> | NTFS | C: | 3 |
>> | 499350761472 | 86123380736 | 413227380736 | | |
>> | NTFS | | 3 |
>> | 633335808 | 543641600 | 89694208 | | |
>> | NTFS | | 3 |
>> | 500913139712 | 106947141632 | 393965998080 | | |
>> | NTFS | | 3 |
>> | 499288895488 | 237867192320 | 261421703168 | | |
>> | FAT32 | | 3 |
>> | 100663296 | 52180992 | 48482304 | | |
>> (5 rows)
>
>
>
On your system, what are the volumes without letters etc? If they're things
like swap/pagefile, recovery partition etc, then they can probably be
omitted (e.g. SELECT ... WHERE mount_point IS NOT NULL OR drive_letter IS
NOT NULL).
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-24 10:47 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-24 10:47 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>
On Mon, 24 Jul 2023 at 15:34, Dave Page <[email protected]> wrote:
> On your system, what are the volumes without letters etc? If they're
> things like swap/pagefile, recovery partition etc, then they can probably
> be omitted (e.g. SELECT ... WHERE mount_point IS NOT NULL OR drive_letter
> IS NOT NULL).
>
On my system, the SSD is assigned the letter C, and I have partitioned my
HDD into two partitions with the letters F and G.
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-24 11:09 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Dave Page @ 2023-07-24 11:09 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>
On Mon, 24 Jul 2023 at 11:47, Sahil Harpal <[email protected]>
wrote:
> On Mon, 24 Jul 2023 at 15:34, Dave Page <[email protected]> wrote:
>
>> On your system, what are the volumes without letters etc? If they're
>> things like swap/pagefile, recovery partition etc, then they can probably
>> be omitted (e.g. SELECT ... WHERE mount_point IS NOT NULL OR drive_letter
>> IS NOT NULL).
>>
>
> On my system, the SSD is assigned the letter C, and I have partitioned my
> HDD into two partitions with the letters F and G.
>
OK, so F and G are not being shown in the query results (or more correctly,
they are there, but the drive letter is showing as NULL?). If so, that
seems like a bug in the extension.
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-25 08:51 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
0 siblings, 0 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-07-25 08:51 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Ashesh Vashi <[email protected]>; pgadmin-hackers; Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>
On Mon, 24 Jul 2023 at 16:39, Dave Page <[email protected]> wrote:
> OK, so F and G are not being shown in the query results (or more
> correctly, they are there, but the drive letter is showing as NULL?). If
> so, that seems like a bug in the extension.
>
Yeah, it's showing NULL for the F and G drives. Can anyone please confirm
this once to just ensure it's not my system's fault?
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-27 07:39 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-07-27 07:39 UTC (permalink / raw)
To: pgadmin-hackers; +Cc: Aditya Toshniwal <[email protected]>; Khushboo Vashi <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
On Wed, 12 Jul 2023 at 15:58, Sahil Harpal <[email protected]>
wrote:
> On Wed, 12 Jul 2023 at 10:47, Khushboo Vashi <
> [email protected]> wrote:
> [image: image.gif]
>
>> It fails while applying. Can you please rebase your patch and send it?
>>
>
> Could you please try the attached patch.
>
Just a follow-up email to check if I can proceed with a similar structure
and logic for the remaining disk tab.
If anyone has faced any issues while using it or has any suggestions for
improvement, please let me know, and I will make the necessary changes and
follow the same approach for the disk tab. Although there is an issue with
drive's letters, the I/O analysis of block devices is working just fine on
my system, so I can probably start working on it parallelly.
Thanks,
Sahil
Attachments:
[image/gif] image.gif (42B, 3-image.gif)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-27 08:29 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-07-27 08:29 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi Sahil,
On Thu, Jul 27, 2023 at 1:09 PM Sahil Harpal <[email protected]>
wrote:
> On Wed, 12 Jul 2023 at 15:58, Sahil Harpal <[email protected]>
> wrote:
>
>> On Wed, 12 Jul 2023 at 10:47, Khushboo Vashi <
>> [email protected]> wrote:
>> [image: image.gif]
>>
>>> It fails while applying. Can you please rebase your patch and send it?
>>>
>>
>> Could you please try the attached patch.
>>
> Just a follow-up email to check if I can proceed with a similar structure
> and logic for the remaining disk tab.
> If anyone has faced any issues while using it or has any suggestions for
> improvement, please let me know, and I will make the necessary changes and
> follow the same approach for the disk tab. Although there is an issue with
> drive's letters, the I/O analysis of block devices is working just fine on
> my system, so I can probably start working on it parallelly.
>
> Some initial review comments.
- While applying the patch, I got warnings; please fix those.
- Fix PEP-8 errors (yarn run pep8)
- Fix the linter errors. Run *yarn run bundle:dev* instead of *yarn run
webpacker so* you will get the linter errors on every run.
- If the System Stat extension does not exist, display the appropriate
message instead of blank graphs.
- For the first time, graphs start from the opposite side, and after the
following API call, those get adjusted; please fix that.
- Please consider the refresh rate for the API calls (preferences settings
> Dashboards > Refresh rates)
The code review is in progress; I will send my review comments once
completed.
Thanks,
Khushboo
> Thanks,
> Sahil
>
>
Attachments:
[image/gif] image.gif (42B, 3-image.gif)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-07-27 10:56 Khushboo Vashi <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-07-27 10:56 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
On Thu, Jul 27, 2023 at 1:59 PM Khushboo Vashi <
[email protected]> wrote:
> Hi Sahil,
>
> On Thu, Jul 27, 2023 at 1:09 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Wed, 12 Jul 2023 at 15:58, Sahil Harpal <[email protected]>
>> wrote:
>>
>>> On Wed, 12 Jul 2023 at 10:47, Khushboo Vashi <
>>> [email protected]> wrote:
>>> [image: image.gif]
>>>
>>>> It fails while applying. Can you please rebase your patch and send it?
>>>>
>>>
>>> Could you please try the attached patch.
>>>
>> Just a follow-up email to check if I can proceed with a similar structure
>> and logic for the remaining disk tab.
>>
> You have made different files tab-wise; it would be more manageable if you
could put them under a subfolder OR start a name with system_state or
something like that.
> If anyone has faced any issues while using it or has any suggestions for
>> improvement, please let me know, and I will make the necessary changes and
>> follow the same approach for the disk tab. Although there is an issue with
>> drive's letters, the I/O analysis of block devices is working just fine on
>> my system, so I can probably start working on it parallelly.
>>
>> Some initial review comments.
>
> - While applying the patch, I got warnings; please fix those.
> - Fix PEP-8 errors (yarn run pep8)
> - Fix the linter errors. Run *yarn run bundle:dev* instead of *yarn run
> webpacker so* you will get the linter errors on every run.
> - If the System Stat extension does not exist, display the appropriate
> message instead of blank graphs.
> - For the first time, graphs start from the opposite side, and after the
> following API call, those get adjusted; please fix that.
> - Please consider the refresh rate for the API calls (preferences settings
> > Dashboards > Refresh rates)
>
> The code review is in progress; I will send my review comments once
> completed.
>
> Thanks,
> Khushboo
>
>
>> Thanks,
>> Sahil
>>
>>
Attachments:
[image/gif] image.gif (42B, 3-image.gif)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-02 12:43 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-02 12:43 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers; Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi Khushboo,
On Thu, 27 Jul 2023 at 16:26, Khushboo Vashi <
[email protected]> wrote:
> You have made different files tab-wise; it would be more manageable if you
> could put them under a subfolder OR start a name with system_state or
> something like that.
> On Thu, Jul 27, 2023 at 1:59 PM Khushboo Vashi <
> [email protected]> wrote:
>
>> Some initial review comments.
>> - While applying the patch, I got warnings; please fix those.
>> - Fix PEP-8 errors (yarn run pep8)
>> - Fix the linter errors. Run *yarn run bundle:dev* instead of *yarn run
>> webpacker so* you will get the linter errors on every run.
>> - If the System Stat extension does not exist, display the appropriate
>> message instead of blank graphs.
>> - For the first time, graphs start from the opposite side, and after the
>> following API call, those get adjusted; please fix that.
>> - Please consider the refresh rate for the API calls (preferences
>> settings > Dashboards > Refresh rates)
>>
>>>
Thank you for the feedback. I have made all the changes as you have
suggested, except for the issue where graphs are starting from opposite
sides. I am using the same StreamingChart component that is already there.
I just made a few changes to handle multiple Y-axes. I tried debugging it,
but haven't found a solution yet. However, I also checked the pgAdmin4
desktop application, and I observed a similar behavior there. The initial
graphs start from the opposite side and then get adjusted automatically,
but the transition is a little faster there. Could you please help identify
what might be causing this issue?
Thank you,
Sahil
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-03 04:02 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-03 04:02 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi Sahil,
On Wed, Aug 2, 2023 at 6:14 PM Sahil Harpal <[email protected]>
wrote:
> Hi Khushboo,
>
> On Thu, 27 Jul 2023 at 16:26, Khushboo Vashi <
> [email protected]> wrote:
>
>> You have made different files tab-wise; it would be more manageable if
>> you could put them under a subfolder OR start a name with system_state or
>> something like that.
>> On Thu, Jul 27, 2023 at 1:59 PM Khushboo Vashi <
>> [email protected]> wrote:
>>
>>> Some initial review comments.
>>> - While applying the patch, I got warnings; please fix those.
>>> - Fix PEP-8 errors (yarn run pep8)
>>> - Fix the linter errors. Run *yarn run bundle:dev* instead of *yarn run
>>> webpacker so* you will get the linter errors on every run.
>>> - If the System Stat extension does not exist, display the appropriate
>>> message instead of blank graphs.
>>> - For the first time, graphs start from the opposite side, and after the
>>> following API call, those get adjusted; please fix that.
>>> - Please consider the refresh rate for the API calls (preferences
>>> settings > Dashboards > Refresh rates)
>>>
>>>>
> Thank you for the feedback. I have made all the changes as you have
> suggested, except for the issue where graphs are starting from opposite
> sides. I am using the same StreamingChart component that is already there.
> I just made a few changes to handle multiple Y-axes. I tried debugging it,
> but haven't found a solution yet. However, I also checked the pgAdmin4
> desktop application, and I observed a similar behavior there. The initial
> graphs start from the opposite side and then get adjusted automatically,
> but the transition is a little faster there. Could you please help identify
> what might be causing this issue?
>
Because it is the default behaviour of streaming. You can add a prop
reverse=true to StreamingChart and change the code ret.reverse(); based on
condition.
Unrelated to this, I think instead of showing disk sizes in bytes you can
show them in a human readable format like below used in Statistics tab of
Tables collection node.
[image: image.png]
>
> Thank you,
> Sahil
>
>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[image/png] image.png (37.9K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-06 07:51 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-06 07:51 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi Aditya,
On Thu, 3 Aug 2023 at 09:32, Aditya Toshniwal <
[email protected]> wrote
> Because it is the default behaviour of streaming. You can add a prop
> reverse=true to StreamingChart and change the code ret.reverse(); based
> on condition.
>
So basically, you are suggesting to add one additional property to the
streaming chart, right? Based on its value, apply ret.reverse(). This means
if reverse is true, apply ret.reverse(), otherwise don't. But it is
required to reverse our data every time, otherwise, I believe the most
recent data point will be at the beginning of our plot.
I've tried one more approach here. If our data array is full, this graph
shifting won't happen. So, if we initialize our data list with null values,
there won't be any visible shift. What do you think about this solution?
Unrelated to this, I think instead of showing disk sizes in bytes you can
> show them in a human readable format like below used in Statistics tab of
> Tables collection node.
>
Sure I'll do this!
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-07 05:30 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-07 05:30 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi Sahil,
On Sun, Aug 6, 2023 at 1:21 PM Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
> On Thu, 3 Aug 2023 at 09:32, Aditya Toshniwal <
> [email protected]> wrote
>
>> Because it is the default behaviour of streaming. You can add a prop
>> reverse=true to StreamingChart and change the code ret.reverse(); based
>> on condition.
>>
> So basically, you are suggesting to add one additional property to the
> streaming chart, right? Based on its value, apply ret.reverse(). This means
> if reverse is true, apply ret.reverse(), otherwise don't. But it is
> required to reverse our data every time, otherwise, I believe the most
> recent data point will be at the beginning of our plot.
> I've tried one more approach here. If our data array is full, this graph
> shifting won't happen. So, if we initialize our data list with null values,
> there won't be any visible shift. What do you think about this solution?
>
I would suggest changing the behaviour in StreamingChart so that it can be
used at other places. If you want to reverse it then you can still do it. I
had achieved reverse direction with the following piece of code. Basically,
creating an array of 75 points, filling it with available points and
reverse. You can change it based on reverse=true flag. In your case, simply
reverse, not need of 75 points.
Array.from(new Array(xRange).keys()),
...(data.datasets?.map((d)=>{
let ret = [...d.data];
ret.reverse();
return ret;
})??{}),
>
> Unrelated to this, I think instead of showing disk sizes in bytes you can
>> show them in a human readable format like below used in Statistics tab of
>> Tables collection node.
>>
> Sure I'll do this!
>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-07 11:17 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-07 11:17 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi Aditya,
On Mon, 7 Aug 2023 at 11:01, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
> I would suggest changing the behaviour in StreamingChart so that it can be
> used at other places. If you want to reverse it then you can still do it. I
> had achieved reverse direction with the following piece of code. Basically,
> creating an array of 75 points, filling it with available points and
> reverse. You can change it based on reverse=true flag. In your case,
> simply reverse, not need of 75 points.
> Array.from(new Array(xRange).keys()),
> ...(data.datasets?.map((d)=>{
> let ret = [...d.data];
> ret.reverse();
> return ret;
> })??{}),
>
I'm a little confused here. So, the code snippet above is exactly what's
used to initialize the data currently.
The expected behavior is that the graph should start from the right side
only. However, currently, when the page loads, for a few seconds, the
graphs are visible on the left side and then shift to the right.
I think we can skip the reverse if we make changes in the statsReducer
method.
action.counter ? action.incoming[label] - action.counterData[label] :
action.incoming[label],
...state[label].slice(0, X_AXIS_LENGTH-1),
Here, we are adding new data to the beginning and selecting the first
X_AXIS_LENGTH-1 datapoints from the previous state. However, an alternative
approach would be to initially take the X_AXIS_LENGTH-1 elements from the
end of the array, then add a new element to the end. This would eliminate
the need to reverse the array.
I believe this reversal isn't causing any issues, but something might be
occurring during graph plotting. Reversing just ensures that the most
recent data point remains on the right within the stream.
Thank you,
Sahil
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-07 11:41 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-07 11:41 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
On Mon, Aug 7, 2023 at 4:47 PM Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
> On Mon, 7 Aug 2023 at 11:01, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>> I would suggest changing the behaviour in StreamingChart so that it can
>> be used at other places. If you want to reverse it then you can still do
>> it. I had achieved reverse direction with the following piece of code.
>> Basically, creating an array of 75 points, filling it with available points
>> and reverse. You can change it based on reverse=true flag. In your case,
>> simply reverse, not need of 75 points.
>> Array.from(new Array(xRange).keys()),
>> ...(data.datasets?.map((d)=>{
>> let ret = [...d.data];
>> ret.reverse();
>> return ret;
>> })??{}),
>>
>
> I'm a little confused here. So, the code snippet above is exactly what's
> used to initialize the data currently.
>
> The expected behavior is that the graph should start from the right side
> only. However, currently, when the page loads, for a few seconds, the
> graphs are visible on the left side and then shift to the right.
>
> I think we can skip the reverse if we make changes in the statsReducer
> method.
> action.counter ? action.incoming[label] - action.counterData[label] :
> action.incoming[label],
> ...state[label].slice(0, X_AXIS_LENGTH-1),
>
> Here, we are adding new data to the beginning and selecting the first
> X_AXIS_LENGTH-1 datapoints from the previous state. However, an alternative
> approach would be to initially take the X_AXIS_LENGTH-1 elements from the
> end of the array, then add a new element to the end. This would eliminate
> the need to reverse the array.
>
> I believe this reversal isn't causing any issues, but something might be
> occurring during graph plotting. Reversing just ensures that the most
> recent data point remains on the right within the stream.
>
OK I thought you wanted the graph to go from left to right. Yeah, I have
seen that glitch a few times (but rare). I think you can ignore it for now,
we can look into it once we get time. It is not a priority.
The reason data reversing and stuff is maintained in StreaminChart is -
Pass the data points and StreamingChart will appear from right to left.
Don't want the source to take efforts to reverse and fill.
>
> Thank you,
> Sahil
>
>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-09 19:07 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-08-09 19:07 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi all,
I have attached the updated patch with recent changes.
New changes:
1. Displayed the appropriate message If the System Stat extension does
not exist.
2. Provided option to configure refresh rates for the API calls
(Preferences settings > Dashboards > Refresh rates).
3. Added I/O analysis of block devices under the storage tab.
4. Fixed PEP-8 errors.
5. Fixed the linter errors.
6. Moved all the system stats related .jsx files under a seperate folder
"dashboard/static/js/SystemStats".
7. Added formatter to convert disk sizes (in bytes) to human readable
format.
Pending Work:
1. Process information -
- Issue: The pg_sys_process_info() query takes much longer (more than
2 mins) to execute and prevents the updation of other graphs and tables.
2. Disk information -
- Issue: The pg_sys_disk_info() query returns NULL value for some of
the drive letters.
3. StreamingChart -
- Issue: Graph shifting glitch. For the first time, graphs start from
the opposite side, and after the following API call or a few
seconds later,
those get adjusted.
I need suggestions for labels for different tables and charts. Also, could
you please clarify the use of the counterData variable, which is used for
some of the charts (tps_stats, ti_stats, to_stats, and bio_stats)?
Thank you,
Sahil
Attachments:
[application/octet-stream] SS_WIP.patch (85.9K, 3-SS_WIP.patch)
download | inline diff:
From 3396bf58925a2009992d74636a39e865d351f7d1 Mon Sep 17 00:00:00 2001
From: Sahil Harpal <[email protected]>
Date: Wed, 9 Aug 2023 18:07:47 +0530
Subject: [PATCH] SS changes without disk and process info
---
web/pgadmin/dashboard/__init__.py | 131 ++++++
web/pgadmin/dashboard/static/js/Dashboard.jsx | 234 +++++++---
.../dashboard/static/js/SystemStats/CPU.jsx | 368 +++++++++++++++
.../static/js/SystemStats/Memory.jsx | 372 +++++++++++++++
.../static/js/SystemStats/Storage.jsx | 329 ++++++++++++++
.../static/js/SystemStats/Summary.jsx | 422 ++++++++++++++++++
.../sql/default/system_statistics.sql | 100 +++++
.../js/components/PgChart/DonutChart.jsx | 70 +++
.../js/components/PgChart/StreamingChart.jsx | 146 ++++--
9 files changed, 2081 insertions(+), 91 deletions(-)
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
create mode 100644 web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
create mode 100644 web/pgadmin/static/js/components/PgChart/DonutChart.jsx
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py
index 1dac54e74..c18f5d3de 100644
--- a/web/pgadmin/dashboard/__init__.py
+++ b/web/pgadmin/dashboard/__init__.py
@@ -112,6 +112,72 @@ class DashboardModule(PgAdminModule):
help_str=help_string
)
+ self.hpc_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'hpc_stats_refresh',
+ gettext("Handle & Process count statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.cu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'cu_stats_refresh',
+ gettext(
+ "Percentage of CPU time used by different process \
+ modes statistics refresh rate"
+ ), 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.la_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'la_stats_refresh',
+ gettext("Average load statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.pcu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'pcu_stats_refresh',
+ gettext("CPU usage per process statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.m_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'm_stats_refresh',
+ gettext("Memory usage statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.sm_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'sm_stats_refresh',
+ gettext("Swap memory usage statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.pmu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'pmu_stats_refresh',
+ gettext("Memory usage per process statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.io_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'io_stats_refresh',
+ gettext("I/O analysis statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
self.display_graphs = self.dashboard_preference.register(
'display', 'show_graphs',
gettext("Show graphs?"), 'boolean', True,
@@ -197,6 +263,12 @@ class DashboardModule(PgAdminModule):
'dashboard.get_prepared_by_database_id',
'dashboard.config',
'dashboard.get_config_by_server_id',
+ 'dashboard.check_system_statistics',
+ 'dashboard.check_system_statistics_sid',
+ 'dashboard.check_system_statistics_did',
+ 'dashboard.system_statistics',
+ 'dashboard.system_statistics_sid',
+ 'dashboard.system_statistics_did',
]
@@ -536,3 +608,62 @@ def terminate_session(sid=None, did=None, pid=None):
response=gettext("Success") if res else gettext("Failed"),
status=200
)
+
+
+# To check whether system stats extesion is present or not
[email protected]('check_extension/system_statistics',
+ endpoint='check_system_statistics', methods=['GET'])
[email protected]('check_extension/system_statistics/<int:sid>',
+ endpoint='check_system_statistics_sid', methods=['GET'])
[email protected]('check_extension/system_statistics/<int:sid>/<int:did>',
+ endpoint='check_system_statistics_did', methods=['GET'])
+@login_required
+@check_precondition
+def check_system_statistics(sid=None, did=None):
+ sql = "SELECT * FROM pg_extension WHERE extname = 'system_stats';"
+ status, res = g.conn.execute_scalar(sql)
+ if not status:
+ return internal_server_error(errormsg=res)
+ data = {}
+ if res is not None:
+ data['ss_present'] = True
+ else:
+ data['ss_present'] = False
+ return ajax_response(
+ response=data,
+ status=200
+ )
+
+
+# System Statistics Backend
[email protected]('/system_statistics',
+ endpoint='system_statistics', methods=['GET'])
[email protected]('/system_statistics/<int:sid>',
+ endpoint='system_statistics_sid', methods=['GET'])
[email protected]('/system_statistics/<int:sid>/<int:did>',
+ endpoint='system_statistics_did', methods=['GET'])
+@login_required
+@check_precondition
+def system_statistics(sid=None, did=None):
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_statistics.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = json.loads(
+ chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 7194fcc10..a032fedec 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -29,6 +29,10 @@ import _ from 'lodash';
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
import TabPanel from '../../../static/js/components/TabPanel';
+import Summary from 'SystemStats/Summary';
+import CPU from 'SystemStats/CPU';
+import Memory from 'SystemStats/Memory';
+import Storage from 'SystemStats/Storage';
function parseData(data) {
let res = [];
@@ -148,12 +152,21 @@ export default function Dashboard({
}) {
const classes = useStyles();
let tabs = [gettext('Sessions'), gettext('Locks'), gettext('Prepared Transactions')];
+ let mainTabs = [gettext('General'), gettext('System Statistics')];
+ let systemStatsTabs = [gettext('Summary'), gettext('CPU'), gettext('Memory'), gettext('Storage')];
const [dashData, setdashData] = useState([]);
const [msg, setMsg] = useState('');
+ const [ssMsg, setSsMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
+ const [mainTabVal, setmainTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
+ const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
+
+ const systemStatsTabChanged = (e, tabVal) => {
+ setSystemStatsTabVal(tabVal);
+ };
if (!did) {
tabs.push(gettext('Configuration'));
@@ -163,6 +176,10 @@ export default function Dashboard({
setTabVal(tabVal);
};
+ const mainTabChanged = (e, tabVal) => {
+ setmainTabVal(tabVal);
+ };
+
const serverConfigColumns = [
{
accessor: 'name',
@@ -745,6 +762,7 @@ export default function Dashboard({
useEffect(() => {
let url,
+ ss_extension_check_url = url_for('dashboard.check_system_statistics'),
message = gettext(
'Please connect to the selected server to view the dashboard.'
);
@@ -770,6 +788,10 @@ export default function Dashboard({
if (did) url += sid + '/' + did;
else url += sid;
+ if (did && !props.dbConnected) return;
+ if (did) ss_extension_check_url += '/' + sid + '/' + did;
+ else ss_extension_check_url += '/' + sid;
+
const api = getApiInstance();
if (node) {
api({
@@ -787,6 +809,20 @@ export default function Dashboard({
// show failed message.
setMsg(gettext('Failed to retrieve data from the server.'));
});
+
+ api({
+ url: ss_extension_check_url,
+ type: 'GET',
+ })
+ .then((res) => {
+ const data = res.data;
+ if(data['ss_present'] == false){
+ setSsMsg(gettext('System stats extension is not installed. You can install the extension in a database using the "CREATE EXTENSION system_stats;" SQL command. Reload the pgAdmin once you installed.'));
+ }
+ })
+ .catch(() => {
+ setSsMsg(gettext('Failed to verify the presence of system stats extension.'));
+ });
} else {
setMsg(message);
}
@@ -867,68 +903,148 @@ export default function Dashboard({
{sid && props.serverConnected ? (
<Box className={classes.dashboardPanel}>
<Box className={classes.emptyPanel}>
- {!_.isUndefined(preferences) && preferences.show_graphs && (
- <Graphs
- key={sid + did}
- preferences={preferences}
- sid={sid}
- did={did}
- pageVisible={props.panelVisible}
- ></Graphs>
- )}
- {!_.isUndefined(preferences) && preferences.show_activity && (
- <Box className={classes.panelContent}>
- <Box
- className={classes.cardHeader}
- title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
- >
- {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ <Box className={classes.panelContent}>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={mainTabVal}
+ onChange={mainTabChanged}
+ >
+ {mainTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
</Box>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
- <Tabs
- value={tabVal}
- onChange={tabChanged}
- >
- {tabs.map((tabValue) => {
- return <Tab key={tabValue} label={tabValue} />;
- })}
- <RefreshButton/>
- </Tabs>
+ {/* General Statistics */}
+ <TabPanel value={mainTabVal} index={0} classNameRoot={classes.tabPanel}>
+ {!_.isUndefined(preferences) && preferences.show_graphs && (
+ <Graphs
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ ></Graphs>
+ )}
+ {!_.isUndefined(preferences) && preferences.show_activity && (
+ <Box className={classes.panelContent}>
+ <Box
+ className={classes.cardHeader}
+ title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
+ >
+ {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ </Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={tabVal}
+ onChange={tabChanged}
+ >
+ {tabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
+ </Box>
+ <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ CustomHeader={CustomActiveOnlyHeader}
+ columns={activityColumns}
+ data={filteredDashData}
+ schema={schemaDict}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databaseLocksColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databasePreparedColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={serverConfigColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ </Box>
+ </Box>
+ )}
+ </TabPanel>
+ {/* System Statistics */}
+ <TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <Box height="100%" display="flex" flexDirection="column">
+ {ssMsg === '' ?
+ <>
+ <Box>
+ <Tabs
+ value={systemStatsTabVal}
+ onChange={systemStatsTabChanged}
+ >
+ {systemStatsTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ </Tabs>
+ </Box>
+ <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
+ <Summary
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <CPU
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
+ <Memory
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
+ <Storage
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ </> :
+ <div className={classes.emptyPanel}>
+ <EmptyPanelMessage text={ssMsg}/>
+ </div>
+ }
</Box>
- <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- CustomHeader={CustomActiveOnlyHeader}
- columns={activityColumns}
- data={filteredDashData}
- schema={schemaDict}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databaseLocksColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databasePreparedColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={serverConfigColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- </Box>
+ </TabPanel>
</Box>
- )}
+ </Box>
</Box>
</Box>
) : showDefaultContents() }
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
new file mode 100644
index 000000000..edc827bfa
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
@@ -0,0 +1,368 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function formatBytes(bytes) {
+ const units = ["B", "KB", "MB", "GB", "TB"];
+ let unitIndex = 0;
+
+ while (bytes >= 1024 && unitIndex < units.length - 1) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ return `${bytes.toFixed(2)} ${units[unitIndex]}`;
+}
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'cu_stats': {'User Normal': [], 'User Niced': [], 'Kernel': [], 'Idle': []},
+ 'la_stats': {'1 min': [], '5 mins': [], '10 mins': [], '15 mins': []},
+ 'pcu_stats': {},
+};
+
+export default function CPU({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [cpuUsageInfo, cpuUsageInfoReduce] = useReducer(statsReducer, chartsDefault['cu_stats']);
+ const [loadAvgInfo, loadAvgInfoReduce] = useReducer(statsReducer, chartsDefault['la_stats']);
+ const [processCpuUsageStats, setProcessCpuUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'CPU Usage',
+ accessor: 'cpu_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['cu_stats_refresh'] != preferences['cu_stats_refresh']) {
+ cpuUsageInfoReduce({reset: chartsDefault['cu_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['la_stats_refresh'] != preferences['la_stats_refresh']) {
+ loadAvgInfoReduce({reset: chartsDefault['la_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['pcu_stats_refresh'] != preferences['pcu_stats_refresh']) {
+ setProcessCpuUsageStats({reset: chartsDefault['pcu_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('cu_stats')){
+ let new_cu_stats = {
+ 'User Normal': data['cu_stats']['usermode_normal_process_percent']?data['cu_stats']['usermode_normal_process_percent']:0,
+ 'User Niced': data['cu_stats']['usermode_niced_process_percent']?data['cu_stats']['usermode_niced_process_percent']:0,
+ 'Kernel': data['cu_stats']['kernelmode_process_percent']?data['cu_stats']['kernelmode_process_percent']:0,
+ 'Idle': data['cu_stats']['idle_mode_percent']?data['cu_stats']['idle_mode_percent']:0,
+ };
+ cpuUsageInfoReduce({incoming: new_cu_stats});
+ }
+
+ if(data.hasOwnProperty('la_stats')){
+ let new_la_stats = {
+ '1 min': data['la_stats']['load_avg_one_minute']?data['la_stats']['load_avg_one_minute']:0,
+ '5 mins': data['la_stats']['load_avg_five_minutes']?data['la_stats']['load_avg_five_minutes']:0,
+ '10 mins': data['la_stats']['load_avg_ten_minutes']?data['la_stats']['load_avg_ten_minutes']:0,
+ '15 mins': data['la_stats']['load_avg_fifteen_minutes']?data['la_stats']['load_avg_fifteen_minutes']:0,
+ };
+ loadAvgInfoReduce({incoming: new_la_stats});
+ }
+
+ if(data.hasOwnProperty('pcu_stats')){
+ let pcu_info_list = [];
+ const pcu_info_obj = data['pcu_stats'];
+ for (const key in pcu_info_obj) {
+ pcu_info_list.push({ icon: '', pid: pcu_info_obj[key]['pid'], name: pcu_info_obj[key]['name'], cpu_usage: formatBytes(pcu_info_obj[key]['cpu_usage']) });
+ }
+
+ setProcessCpuUsageStats(pcu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ cpuUsageInfoReduce({reset:chartsDefault['cu_stats']});
+ loadAvgInfoReduce({reset:chartsDefault['la_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <CPUWrapper
+ cpuUsageInfo={transformData(cpuUsageInfo, preferences['cu_stats_refresh'])}
+ loadAvgInfo={transformData(loadAvgInfo, preferences['la_stats_refresh'])}
+ processCpuUsageStats={processCpuUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+CPU.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function CPUWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('CPU Usage ()')}</div>
+ <ChartContainer id='cu-graph' title={gettext('')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.cpuUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Load Average')}</div>
+ <ChartContainer id='la-graph' title={gettext('')} datasets={props.loadAvgInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.loadAvgInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processCpuUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+CPUWrapper.propTypes = {
+ cpuUsageInfo: propTypeStats.isRequired,
+ loadAvgInfo: propTypeStats.isRequired,
+ processCpuUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
new file mode 100644
index 000000000..7a5d029cb
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
@@ -0,0 +1,372 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function formatBytes(bytes) {
+ const units = ["B", "KB", "MB", "GB", "TB"];
+ let unitIndex = 0;
+
+ while (bytes >= 1024 && unitIndex < units.length - 1) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ return `${bytes.toFixed(2)} ${units[unitIndex]}`;
+}
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'sm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'pmu_stats': {},
+};
+
+export default function Memory({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [memoryUsageInfo, memoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['m_stats']);
+ const [swapMemoryUsageInfo, swapMemoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['sm_stats']);
+ const [processMemoryUsageStats, setProcessMemoryUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Usage',
+ accessor: 'memory_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Bytes',
+ accessor: 'memory_bytes',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['m_stats_refresh'] != preferences['m_stats_refresh']) {
+ memoryUsageInfoReduce({reset: chartsDefault['m_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['sm_stats_refresh'] != preferences['sm_stats_refresh']) {
+ swapMemoryUsageInfoReduce({reset: chartsDefault['sm_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['pmu_stats_refresh'] != preferences['pmu_stats_refresh']) {
+ setProcessMemoryUsageStats({reset: chartsDefault['pmu_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('m_stats')){
+ let new_m_stats = {
+ 'Total': data['m_stats']['total_memory']?data['m_stats']['total_memory']:0,
+ 'Used': data['m_stats']['used_memory']?data['m_stats']['used_memory']:0,
+ 'Free': data['m_stats']['free_memory']?data['m_stats']['free_memory']:0,
+ };
+ memoryUsageInfoReduce({incoming: new_m_stats});
+ }
+
+ if(data.hasOwnProperty('sm_stats')){
+ let new_sm_stats = {
+ 'Total': data['sm_stats']['swap_total']?data['sm_stats']['swap_total']:0,
+ 'Used': data['sm_stats']['swap_used']?data['sm_stats']['swap_used']:0,
+ 'Free': data['sm_stats']['swap_free']?data['sm_stats']['swap_free']:0,
+ };
+ swapMemoryUsageInfoReduce({incoming: new_sm_stats});
+ }
+
+ if(data.hasOwnProperty('pmu_stats')){
+ let pmu_info_list = [];
+ const pmu_info_obj = data['pmu_stats'];
+ for (const key in pmu_info_obj) {
+ pmu_info_list.push({ icon: '', pid: pmu_info_obj[key]['pid'], name: pmu_info_obj[key]['name'], memory_usage: formatBytes(pmu_info_obj[key]['memory_usage']), memory_bytes: formatBytes(pmu_info_obj[key]['memory_bytes']) });
+ }
+
+ setProcessMemoryUsageStats(pmu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ memoryUsageInfoReduce({reset:chartsDefault['m_stats']});
+ swapMemoryUsageInfoReduce({reset:chartsDefault['sm_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <MemoryWrapper
+ memoryUsageInfo={transformData(memoryUsageInfo, preferences['m_stats_refresh'])}
+ swapMemoryUsageInfo={transformData(swapMemoryUsageInfo, preferences['sm_stats_refresh'])}
+ processMemoryUsageStats={processMemoryUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Memory.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function MemoryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Memory')}</div>
+ <ChartContainer id='m-graph' title={gettext('')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.memoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Swap Memory')}</div>
+ <ChartContainer id='sm-graph' title={gettext('')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processMemoryUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+MemoryWrapper.propTypes = {
+ memoryUsageInfo: propTypeStats.isRequired,
+ swapMemoryUsageInfo: propTypeStats.isRequired,
+ processMemoryUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
new file mode 100644
index 000000000..f1663df84
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
@@ -0,0 +1,329 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ ioDiskContainer: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+ chartHeader: {
+ fontSize: '14px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function ioStatsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(disk_stats => {
+ newState[disk_stats] = {};
+ Object.keys(action.incoming[disk_stats]).forEach(label => {
+ if(state[disk_stats][label]) {
+ newState[disk_stats][label] = [
+ action.counter ? action.incoming[disk_stats][label] - action.counterData[disk_stats][label] : action.incoming[disk_stats][label],
+ ...state[disk_stats][label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[disk_stats][label] = [
+ action.counter ? action.incoming[disk_stats][label] - action.counterData[disk_stats][label] : action.incoming[disk_stats][label],
+ ];
+ }
+ });
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'io_stats': {},
+};
+
+export default function Storage({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [ioInfo, ioInfoReduce] = useReducer(ioStatsReducer, chartsDefault['io_stats']);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['io_stats_refresh'] != preferences['io_stats_refresh']) {
+ ioInfoReduce({reset: chartsDefault['io_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('io_stats')){
+ const io_info_obj = data['io_stats'];
+ for (const disk in io_info_obj) {
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_total_rw`)){
+ chartsDefault.io_stats[`${disk}_total_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_total_rw`)){
+ ioInfo[`${disk}_total_rw`] = {'Read': [], 'Write': []};
+ }
+
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_bytes_rw`)){
+ chartsDefault.io_stats[`${disk}_bytes_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_bytes_rw`)){
+ ioInfo[`${disk}_bytes_rw`] = {'Read': [], 'Write': []};
+ }
+
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_time_rw`)){
+ chartsDefault.io_stats[`${disk}_time_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_time_rw`)){
+ ioInfo[`${disk}_time_rw`] = {'Read': [], 'Write': []};
+ }
+ }
+
+ let new_io_stats = {};
+ for (const disk in io_info_obj) {
+ new_io_stats[`${disk}_total_rw`] = {'Read': io_info_obj[`${disk}`]['total_reads']?io_info_obj[`${disk}`]['total_reads']:0, 'Write': io_info_obj[`${disk}`]['total_writes']?io_info_obj[`${disk}`]['total_writes']:0};
+ new_io_stats[`${disk}_bytes_rw`] = {'Read': io_info_obj[`${disk}`]['read_bytes']?io_info_obj[`${disk}`]['read_bytes']:0, 'Write': io_info_obj[`${disk}`]['write_bytes']?io_info_obj[`${disk}`]['write_bytes']:0};
+ new_io_stats[`${disk}_time_rw`] = {'Read': io_info_obj[`${disk}`]['read_time_ms']?io_info_obj[`${disk}`]['read_time_ms']:0, 'Write': io_info_obj[`${disk}`]['write_time_ms']?io_info_obj[`${disk}`]['write_time_ms']:0};
+ }
+ ioInfoReduce({incoming: new_io_stats});
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ ioInfoReduce({reset:chartsDefault['io_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <StorageWrapper
+ ioInfo={ioInfo}
+ ioRefreshRate={preferences['io_stats_refresh']}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Storage.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function StorageWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+
+ const keys = Object.keys(props.ioInfo);
+ return (
+ <>
+ {keys.map((key, index) => (
+ index % 3 === 0 && (
+ <Grid key={`disk-${index}`} container spacing={1} className={classes.container}>
+ <Grid container spacing={1} className={classes.ioDiskContainer}>
+ <div className={classes.containerHeader}>{gettext(`Disk ${Math.floor(index / 3) + 1}`)}</div>
+ </Grid>
+ <Grid container spacing={1} className={classes.ioDiskContainer}>
+ {keys.slice(index, index + 3).map((innerKey, innerKeyIndex) => (
+ <Grid key={`${innerKey}`} item md={4} sm={6}>
+ <div className={classes.chartHeader}>{innerKeyIndex==0 ? gettext('I/O Operations Count'): innerKeyIndex==1? gettext('Data Transfer (Bytes)'):gettext('Time Spent in I/O Operations (Milliseconds)')}</div>
+ <ChartContainer id={`io-graph-${innerKey}`} title={gettext('')} datasets={transformData(props.ioInfo[innerKey], props.ioRefreshRate).datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={transformData(props.ioInfo[innerKey], props.ioRefreshRate)} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ ))}
+ </Grid>
+ </Grid>
+ )
+ ))}
+ </>
+ );
+}
+
+StorageWrapper.propTypes = {
+ ioInfo: PropTypes.objectOf(
+ PropTypes.shape({
+ Read: PropTypes.array,
+ Write: PropTypes.array,
+ })
+ ),
+ ioRefreshRate: PropTypes.number.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
new file mode 100644
index 000000000..b8a9a33b3
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
@@ -0,0 +1,422 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import DonutChart from '../../../../static/js/components/PgChart/DonutChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ table: {
+ width: '100%',
+ backgroundColor: theme.otherVars.tableBg,
+ border: '1px solid rgb(221, 224, 230)',
+ },
+ tableVal: {
+ border: '1px solid rgb(221, 224, 230) !important',
+ padding: '10px !important',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'hpc_stats': {'Handle': new Array(X_AXIS_LENGTH).fill(null), 'Process': new Array(X_AXIS_LENGTH).fill(null)},
+};
+
+const SummaryTable = (props) => {
+ const classes = useStyles();
+ const data = props.data;
+ return (
+ <table className={classes.table}>
+ <thead>
+ <tr>
+ <th className={classes.tableVal}>Property</th>
+ <th className={classes.tableVal}>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {data.map((item, index) => (
+ <tr className={classes.tableVal} key={index}>
+ <td className={classes.tableVal}>{item.name}</td>
+ <td className={classes.tableVal}>{item.value}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+};
+
+SummaryTable.propTypes = {
+ data: PropTypes.any,
+};
+
+export default function Summary({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [processHandleCount, processHandleCountReduce] = useReducer(statsReducer, chartsDefault['hpc_stats']);
+ const [osStats, setOsStats] = useState([]);
+ const [cpuStats, setCpuStats] = useState([]);
+ const [processInfoStats] = useState({'Running': 4, 'Sleeping': 2, 'Stopped': 1, 'Zombie': 2});
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [longPollDelay] = useState(180000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'Property',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Value',
+ accessor: 'value',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['hpc_stats_refresh'] != preferences['hpc_stats_refresh']) {
+ processHandleCountReduce({reset: chartsDefault['hpc_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useEffect(() => {
+ try {
+ // Fetch the latest data point from the API endpoint
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'pg_sys_os_info,pg_sys_cpu_info';
+ const api = getApiInstance();
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ let data = res.data;
+
+ const os_info_obj = data['pg_sys_os_info'];
+ let os_info_list = [
+ { icon: '', name: 'Name', value: os_info_obj['name'] },
+ { icon: '', name: 'Version', value: os_info_obj['version'] },
+ { icon: '', name: 'Host name', value: os_info_obj['host_name'] },
+ { icon: '', name: 'Domain name', value: os_info_obj['domain_name'] },
+ { icon: '', name: 'Architecture', value: os_info_obj['architecture'] },
+ { icon: '', name: 'Os up since seconds', value: os_info_obj['os_up_since_seconds'] },
+ ];
+ setOsStats(os_info_list);
+
+ const cpu_info_obj = data['pg_sys_cpu_info'];
+ let cpu_info_list = [
+ { icon: '', name: 'Vendor', value: cpu_info_obj['vendor'] },
+ { icon: '', name: 'Description', value: cpu_info_obj['description'] },
+ { icon: '', name: 'Model name', value: cpu_info_obj['model_name'] },
+ { icon: '', name: 'No of cores', value: cpu_info_obj['no_of_cores'] },
+ { icon: '', name: 'Architecture', value: cpu_info_obj['architecture'] },
+ { icon: '', name: 'Clock speed Hz', value: cpu_info_obj['clock_speed_hz'] },
+ { icon: '', name: 'L1 dcache size', value: cpu_info_obj['l1dcache_size'] },
+ { icon: '', name: 'L1 icache size', value: cpu_info_obj['l1icache_size'] },
+ { icon: '', name: 'L2 cache size', value: cpu_info_obj['l2cache_size'] },
+ { icon: '', name: 'L3 cache size', value: cpu_info_obj['l3cache_size'] },
+ ];
+ setCpuStats(cpu_info_list);
+
+ setErrorMsg(null);
+ })
+ .catch((error) => {
+ console.error('Error fetching data:', error);
+ });
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ }, [sid, did, enablePoll, pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ processHandleCountReduce({incoming: data['hpc_stats']});
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ processHandleCountReduce({reset:chartsDefault['hpc_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ useInterval(()=>{
+ // let url;
+ // url = url_for('dashboard.system_statistics');
+ // url += '/' + sid;
+ // url += did > 0 ? '/' + did : '';
+ // url += '?chart_names=' + 'pi_stats';
+ // axios.get(url)
+ // .then((resp)=>{
+ // let data = resp.data;
+ // console.log("pi data: ", data);
+ // })
+ // .catch((error)=>{
+ // if(!errorMsg) {
+ // if(error.response) {
+ // if (error.response.status === 428) {
+ // setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ // } else {
+ // setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ // }
+ // } else if(error.request) {
+ // setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ // return;
+ // } else {
+ // console.error(error);
+ // }
+ // }
+ // });
+ }, enablePoll ? longPollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <SummaryWrapper
+ processHandleCount={transformData(processHandleCount, preferences['hpc_stats_refresh'])}
+ osStats={osStats}
+ cpuStats={cpuStats}
+ processInfoStats={transformData(processInfoStats, 5)}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Summary.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function SummaryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('OS Information')}</div>
+ <SummaryTable data={props.osStats} />
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Handle & Process Count')}</div>
+ <ChartContainer id='hpc-graph' title={gettext('')} datasets={props.processHandleCount.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.processHandleCount} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} showSecondAxis={true} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('CPU Information')}</div>
+ <SummaryTable data={props.cpuStats} />
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Process Information')}</div>
+ <ChartContainer id='pi-graph' title={gettext('')} datasets={props.processInfoStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <DonutChart data={props.processInfoStats.datasets} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ </>
+ );
+}
+
+SummaryWrapper.propTypes = {
+ processHandleCount: PropTypes.any.isRequired,
+ osStats: PropTypes.any.isRequired,
+ cpuStats: PropTypes.any.isRequired,
+ processInfoStats: PropTypes.any.isRequired,
+ tableHeader: PropTypes.any.isRequired,
+ errorMsg: PropTypes.any,
+ showTooltip: PropTypes.bool,
+ showDataPoints: PropTypes.bool,
+ lineBorderWidth: PropTypes.number,
+ isDatabase: PropTypes.bool,
+ isTest: PropTypes.bool,
+};
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
new file mode 100644
index 000000000..131b6a3df
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
@@ -0,0 +1,100 @@
+{% set add_union = false %}
+{% if 'pg_sys_os_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_os_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_os_info()) t
+{% endif %}
+{% if add_union and 'pg_sys_cpu_info' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pg_sys_cpu_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_cpu_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_info()) t
+{% endif %}
+{% if add_union and 'hpc_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'hpc_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'hpc_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT
+ (SELECT handle_count FROM pg_sys_os_info()) AS "{{ _('Handle') }}",
+ (SELECT process_count FROM pg_sys_os_info()) AS "{{ _('Process') }}"
+ ) t
+{% endif %}
+{% if add_union and 'cu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'cu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'cu_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_usage_info()) t
+{% endif %}
+{% if add_union and 'la_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'la_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'la_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_load_avg_info()) t
+{% endif %}
+{% if add_union and 'pcu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pcu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pcu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, cpu_usage, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT total_memory, used_memory, free_memory FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'sm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'sm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'sm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT swap_total, swap_used, swap_free FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'pmu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pmu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pmu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, memory_usage, memory_bytes, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'io_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'io_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'io_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('disk'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT *, ROW_NUMBER() OVER (ORDER BY device_name) AS row_number
+ FROM pg_sys_io_analysis_info()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'pi_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pi_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pi_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_process_info()) t
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/DonutChart.jsx b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
new file mode 100644
index 000000000..b28e8e66d
--- /dev/null
+++ b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
@@ -0,0 +1,70 @@
+import React, { useEffect, useRef } from 'react';
+import Chart from 'chart.js/auto';
+import PropTypes from 'prop-types';
+
+export default function DonutChart({ data }) {
+ const chartRef = useRef(null);
+ const chartInstance = useRef(null);
+
+ useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ if (chartInstance.current) {
+ // If chart instance exists, update the data
+ chartInstance.current.data.labels = data.map((item) => item.label);
+ chartInstance.current.data.datasets[0].data = data.map((item) => item.data);
+ chartInstance.current.update();
+ } else {
+ // If chart instance doesn't exist, create a new chart
+ const chartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: false, // Hide the labels at the top
+ },
+ },
+ animation: {
+ duration: 0, // Disable the animation
+ },
+ tooltips: {
+ callbacks: {
+ label: function (tooltipItem, chartData) {
+ const dataset = chartData.datasets[tooltipItem.datasetIndex];
+ const total = dataset.data.reduce((previousValue, currentValue) => previousValue + currentValue);
+ const currentValue = dataset.data[tooltipItem.index];
+ const percentage = ((currentValue / total) * 100).toFixed(2) + '%';
+ return dataset.label + ': ' + currentValue + ' (' + percentage + ')';
+ },
+ },
+ },
+ };
+
+ const chartData = {
+ labels: data.map((item) => item.label),
+ datasets: [
+ {
+ data: data.map((item) => item.data),
+ backgroundColor: data.map((item) => item.borderColor),
+ hoverBackgroundColor: data.map((item) => item.borderColor),
+ },
+ ],
+ };
+
+ const ctx = chartRef.current.getContext('2d');
+ chartInstance.current = new Chart(ctx, {
+ type: 'doughnut',
+ data: chartData,
+ options: chartOptions,
+ });
+ }
+ }
+ }, [data]);
+
+ return (
+ <canvas ref={chartRef} />
+ );
+}
+
+DonutChart.propTypes = {
+ data: PropTypes.array.isRequired,
+};
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
index bd465e3da..fb90a37e8 100644
--- a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
+++ b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
@@ -32,7 +32,7 @@ function tooltipPlugin(refreshRate) {
showTooltip();
let tooltipHtml=`<div>${(u.data[1].length-1-parseInt(u.legend.values[0]['_'])) * refreshRate + gettext(' seconds ago')}</div>`;
for(let i=1; i<u.series.length; i++) {
- tooltipHtml += `<div class="uplot-tooltip-label"><div style="height:12px; width:12px; background-color:${u.series[i].stroke()}"></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
+ tooltipHtml += `<div class='uplot-tooltip-label'><div style='height:12px; width:12px; background-color:${u.series[i].stroke()}'></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
}
tooltip.innerHTML = tooltipHtml;
@@ -58,44 +58,89 @@ function tooltipPlugin(refreshRate) {
};
}
-export default function StreamingChart({xRange=75, data, options}) {
+export default function StreamingChart({xRange=75, data, options, showSecondAxis=false}) {
const chartRef = useRef();
const theme = useTheme();
const { width, height, ref:containerRef } = useResizeDetector();
- const defaultOptions = useMemo(()=>({
- title: '',
- width: width,
- height: height,
- padding: [10, 0, 10, 0],
- focus: {
- alpha: 0.3,
- },
- cursor: {
- y: false,
- drag: {
- setScale: false,
- }
- },
- series: [
+ const defaultOptions = useMemo(()=> {
+ const series = [
{},
- ...(data.datasets?.map((datum)=>({
+ ...(data.datasets?.map((datum, index) => ({
label: datum.label,
stroke: datum.borderColor,
width: options.lineBorderWidth ?? 1,
- points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius*2 }
- }))??{})
- ],
- scales: {
- x: {
- time: false,
- }
- },
- axes: [
+ scale: showSecondAxis && (index === 1) ? 'y1' : 'y',
+ points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius * 2 },
+ })) ?? []),
+ ];
+
+ const axes = [
{
show: false,
stroke: theme.palette.text.primary,
},
- {
+ ];
+
+ if(showSecondAxis){
+ axes.push({
+ scale: 'y',
+ grid: {
+ stroke: theme.otherVars.borderColor,
+ width: 0.5,
+ },
+ stroke: theme.palette.text.primary,
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ axes.push({
+ scale: 'y1',
+ side: 1,
+ stroke: theme.palette.text.primary,
+ grid: {show: false},
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ } else{
+ axes.push({
+ scale: 'y',
grid: {
stroke: theme.otherVars.borderColor,
width: 0.5,
@@ -108,11 +153,47 @@ export default function StreamingChart({xRange=75, data, options}) {
if(size < 40) size = 40;
}
return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ }
+
+ return {
+ title: '',
+ width: width,
+ height: height,
+ padding: [10, 0, 10, 0],
+ focus: {
+ alpha: 0.3,
+ },
+ cursor: {
+ y: false,
+ drag: {
+ setScale: false,
+ }
+ },
+ series: series,
+ scales: {
+ x: {
+ time: false,
}
- }
- ],
- plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
- }), [data.refreshRate, data?.datasets?.length, width, height, options]);
+ },
+ axes: axes,
+ plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
+ };
+ }, [data.refreshRate, data?.datasets?.length, width, height, options]);
const initialState = [
Array.from(new Array(xRange).keys()),
@@ -140,4 +221,5 @@ StreamingChart.propTypes = {
xRange: PropTypes.number.isRequired,
data: propTypeData.isRequired,
options: PropTypes.object,
+ showSecondAxis: PropTypes.bool,
};
--
2.41.0.windows.1
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-16 02:59 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 0 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-08-16 02:59 UTC (permalink / raw)
To: pgadmin-hackers; +Cc: Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Hi,
This email is regarding the recent patch I shared with you. Have you
encountered any issues or identified areas for improvement? Additionally,
could you assist me in resolving the challenges I'm currently facing and
provide suggestions for chart titles?
Thanks,
Sahil
On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
wrote:
> Hi all,
>
> I have attached the updated patch with recent changes.
>
> New changes:
>
> 1. Displayed the appropriate message If the System Stat extension does
> not exist.
> 2. Provided option to configure refresh rates for the API calls
> (Preferences settings > Dashboards > Refresh rates).
> 3. Added I/O analysis of block devices under the storage tab.
> 4. Fixed PEP-8 errors.
> 5. Fixed the linter errors.
> 6. Moved all the system stats related .jsx files under a
> seperate folder "dashboard/static/js/SystemStats".
> 7. Added formatter to convert disk sizes (in bytes) to human readable
> format.
>
> Pending Work:
>
> 1. Process information -
> - Issue: The pg_sys_process_info() query takes much longer (more
> than 2 mins) to execute and prevents the updation of other graphs and
> tables.
> 2. Disk information -
> - Issue: The pg_sys_disk_info() query returns NULL value for some
> of the drive letters.
> 3. StreamingChart -
> - Issue: Graph shifting glitch. For the first time, graphs start
> from the opposite side, and after the following API call or a few seconds
> later, those get adjusted.
>
> I need suggestions for labels for different tables and charts. Also, could
> you please clarify the use of the counterData variable, which is used for
> some of the charts (tps_stats, ti_stats, to_stats, and bio_stats)?
>
> Thank you,
> Sahil
>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-16 04:18 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-16 04:18 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Can you please rebase the patch and send it again?
On Thu, Aug 10, 2023 at 12:37 AM Sahil Harpal <[email protected]>
wrote:
> Hi all,
>
> I have attached the updated patch with recent changes.
>
> New changes:
>
> 1. Displayed the appropriate message If the System Stat extension does
> not exist.
> 2. Provided option to configure refresh rates for the API calls
> (Preferences settings > Dashboards > Refresh rates).
> 3. Added I/O analysis of block devices under the storage tab.
> 4. Fixed PEP-8 errors.
> 5. Fixed the linter errors.
> 6. Moved all the system stats related .jsx files under a
> seperate folder "dashboard/static/js/SystemStats".
> 7. Added formatter to convert disk sizes (in bytes) to human readable
> format.
>
> Pending Work:
>
> 1. Process information -
> - Issue: The pg_sys_process_info() query takes much longer (more
> than 2 mins) to execute and prevents the updation of other graphs and
> tables.
> 2. Disk information -
> - Issue: The pg_sys_disk_info() query returns NULL value for some
> of the drive letters.
> 3. StreamingChart -
> - Issue: Graph shifting glitch. For the first time, graphs start
> from the opposite side, and after the following API call or a few seconds
> later, those get adjusted.
>
> I need suggestions for labels for different tables and charts. Also, could
> you please clarify the use of the counterData variable, which is used for
> some of the charts (tps_stats, ti_stats, to_stats, and bio_stats)?
>
> Thank you,
> Sahil
>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-16 05:38 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-08-16 05:38 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
Could you please try this attached patch.
Attachments:
[application/octet-stream] SS_WIP.patch (84.5K, 3-SS_WIP.patch)
download | inline diff:
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py
index 1dac54e74..c18f5d3de 100644
--- a/web/pgadmin/dashboard/__init__.py
+++ b/web/pgadmin/dashboard/__init__.py
@@ -112,6 +112,72 @@ class DashboardModule(PgAdminModule):
help_str=help_string
)
+ self.hpc_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'hpc_stats_refresh',
+ gettext("Handle & Process count statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.cu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'cu_stats_refresh',
+ gettext(
+ "Percentage of CPU time used by different process \
+ modes statistics refresh rate"
+ ), 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.la_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'la_stats_refresh',
+ gettext("Average load statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.pcu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'pcu_stats_refresh',
+ gettext("CPU usage per process statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.m_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'm_stats_refresh',
+ gettext("Memory usage statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.sm_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'sm_stats_refresh',
+ gettext("Swap memory usage statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.pmu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'pmu_stats_refresh',
+ gettext("Memory usage per process statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.io_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'io_stats_refresh',
+ gettext("I/O analysis statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
self.display_graphs = self.dashboard_preference.register(
'display', 'show_graphs',
gettext("Show graphs?"), 'boolean', True,
@@ -197,6 +263,12 @@ class DashboardModule(PgAdminModule):
'dashboard.get_prepared_by_database_id',
'dashboard.config',
'dashboard.get_config_by_server_id',
+ 'dashboard.check_system_statistics',
+ 'dashboard.check_system_statistics_sid',
+ 'dashboard.check_system_statistics_did',
+ 'dashboard.system_statistics',
+ 'dashboard.system_statistics_sid',
+ 'dashboard.system_statistics_did',
]
@@ -536,3 +608,62 @@ def terminate_session(sid=None, did=None, pid=None):
response=gettext("Success") if res else gettext("Failed"),
status=200
)
+
+
+# To check whether system stats extesion is present or not
[email protected]('check_extension/system_statistics',
+ endpoint='check_system_statistics', methods=['GET'])
[email protected]('check_extension/system_statistics/<int:sid>',
+ endpoint='check_system_statistics_sid', methods=['GET'])
[email protected]('check_extension/system_statistics/<int:sid>/<int:did>',
+ endpoint='check_system_statistics_did', methods=['GET'])
+@login_required
+@check_precondition
+def check_system_statistics(sid=None, did=None):
+ sql = "SELECT * FROM pg_extension WHERE extname = 'system_stats';"
+ status, res = g.conn.execute_scalar(sql)
+ if not status:
+ return internal_server_error(errormsg=res)
+ data = {}
+ if res is not None:
+ data['ss_present'] = True
+ else:
+ data['ss_present'] = False
+ return ajax_response(
+ response=data,
+ status=200
+ )
+
+
+# System Statistics Backend
[email protected]('/system_statistics',
+ endpoint='system_statistics', methods=['GET'])
[email protected]('/system_statistics/<int:sid>',
+ endpoint='system_statistics_sid', methods=['GET'])
[email protected]('/system_statistics/<int:sid>/<int:did>',
+ endpoint='system_statistics_did', methods=['GET'])
+@login_required
+@check_precondition
+def system_statistics(sid=None, did=None):
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_statistics.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = json.loads(
+ chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 7194fcc10..e6afeff07 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -29,6 +29,10 @@ import _ from 'lodash';
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
import TabPanel from '../../../static/js/components/TabPanel';
+import Summary from 'SystemStats/Summary';
+import CPU from 'SystemStats/CPU';
+import Memory from 'SystemStats/Memory';
+import Storage from 'SystemStats/Storage';
function parseData(data) {
let res = [];
@@ -148,12 +152,21 @@ export default function Dashboard({
}) {
const classes = useStyles();
let tabs = [gettext('Sessions'), gettext('Locks'), gettext('Prepared Transactions')];
+ let mainTabs = [gettext('General'), gettext('System Statistics')];
+ let systemStatsTabs = [gettext('Summary'), gettext('CPU'), gettext('Memory'), gettext('Storage')];
const [dashData, setdashData] = useState([]);
const [msg, setMsg] = useState('');
+ const [ssMsg, setSsMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
+ const [mainTabVal, setmainTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
+ const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
+
+ const systemStatsTabChanged = (e, tabVal) => {
+ setSystemStatsTabVal(tabVal);
+ };
if (!did) {
tabs.push(gettext('Configuration'));
@@ -163,6 +176,10 @@ export default function Dashboard({
setTabVal(tabVal);
};
+ const mainTabChanged = (e, tabVal) => {
+ setmainTabVal(tabVal);
+ };
+
const serverConfigColumns = [
{
accessor: 'name',
@@ -745,6 +762,7 @@ export default function Dashboard({
useEffect(() => {
let url,
+ ss_extension_check_url = url_for('dashboard.check_system_statistics'),
message = gettext(
'Please connect to the selected server to view the dashboard.'
);
@@ -770,6 +788,10 @@ export default function Dashboard({
if (did) url += sid + '/' + did;
else url += sid;
+ if (did && !props.dbConnected) return;
+ if (did) ss_extension_check_url += '/' + sid + '/' + did;
+ else ss_extension_check_url += '/' + sid;
+
const api = getApiInstance();
if (node) {
api({
@@ -787,6 +809,20 @@ export default function Dashboard({
// show failed message.
setMsg(gettext('Failed to retrieve data from the server.'));
});
+
+ api({
+ url: ss_extension_check_url,
+ type: 'GET',
+ })
+ .then((res) => {
+ const data = res.data;
+ if(data['ss_present'] == false){
+ setSsMsg(gettext('System stats extension is not installed. You can install the extension in a database using the "CREATE EXTENSION system_stats;" SQL command. Reload the pgAdmin once you installed.'));
+ }
+ })
+ .catch(() => {
+ setSsMsg(gettext('Failed to verify the presence of system stats extension.'));
+ });
} else {
setMsg(message);
}
@@ -867,68 +903,148 @@ export default function Dashboard({
{sid && props.serverConnected ? (
<Box className={classes.dashboardPanel}>
<Box className={classes.emptyPanel}>
- {!_.isUndefined(preferences) && preferences.show_graphs && (
- <Graphs
- key={sid + did}
- preferences={preferences}
- sid={sid}
- did={did}
- pageVisible={props.panelVisible}
- ></Graphs>
- )}
- {!_.isUndefined(preferences) && preferences.show_activity && (
- <Box className={classes.panelContent}>
- <Box
- className={classes.cardHeader}
- title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
- >
- {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ <Box className={classes.panelContent}>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={mainTabVal}
+ onChange={mainTabChanged}
+ >
+ {mainTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
</Box>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
- <Tabs
- value={tabVal}
- onChange={tabChanged}
- >
- {tabs.map((tabValue) => {
- return <Tab key={tabValue} label={tabValue} />;
- })}
- <RefreshButton/>
- </Tabs>
+ {/* General Statistics */}
+ <TabPanel value={mainTabVal} index={0} classNameRoot={classes.tabPanel}>
+ {!_.isUndefined(preferences) && preferences.show_graphs && (
+ <Graphs
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ ></Graphs>
+ )}
+ {!_.isUndefined(preferences) && preferences.show_activity && (
+ <Box className={classes.panelContent}>
+ <Box
+ className={classes.cardHeader}
+ title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
+ >
+ {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ </Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={tabVal}
+ onChange={tabChanged}
+ >
+ {tabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
+ </Box>
+ <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ CustomHeader={CustomActiveOnlyHeader}
+ columns={activityColumns}
+ data={filteredDashData}
+ schema={schemaDict}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databaseLocksColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databasePreparedColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={serverConfigColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ </Box>
+ </Box>
+ )}
+ </TabPanel>
+ {/* System Statistics */}
+ <TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <Box height="100%" display="flex" flexDirection="column">
+ {ssMsg === '' ?
+ <>
+ <Box>
+ <Tabs
+ value={systemStatsTabVal}
+ onChange={systemStatsTabChanged}
+ >
+ {systemStatsTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ </Tabs>
+ </Box>
+ <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
+ <Summary
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <CPU
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
+ <Memory
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
+ <Storage
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ </> :
+ <div className={classes.emptyPanel}>
+ <EmptyPanelMessage text={ssMsg}/>
+ </div>
+ }
</Box>
- <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- CustomHeader={CustomActiveOnlyHeader}
- columns={activityColumns}
- data={filteredDashData}
- schema={schemaDict}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databaseLocksColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databasePreparedColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={serverConfigColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- </Box>
+ </TabPanel>
</Box>
- )}
+ </Box>
</Box>
</Box>
) : showDefaultContents() }
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
new file mode 100644
index 000000000..60a4ffa7e
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
@@ -0,0 +1,368 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function formatBytes(bytes) {
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ let unitIndex = 0;
+
+ while (bytes >= 1024 && unitIndex < units.length - 1) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ return `${bytes.toFixed(2)} ${units[unitIndex]}`;
+}
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'cu_stats': {'User Normal': [], 'User Niced': [], 'Kernel': [], 'Idle': []},
+ 'la_stats': {'1 min': [], '5 mins': [], '10 mins': [], '15 mins': []},
+ 'pcu_stats': {},
+};
+
+export default function CPU({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [cpuUsageInfo, cpuUsageInfoReduce] = useReducer(statsReducer, chartsDefault['cu_stats']);
+ const [loadAvgInfo, loadAvgInfoReduce] = useReducer(statsReducer, chartsDefault['la_stats']);
+ const [processCpuUsageStats, setProcessCpuUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'CPU Usage',
+ accessor: 'cpu_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['cu_stats_refresh'] != preferences['cu_stats_refresh']) {
+ cpuUsageInfoReduce({reset: chartsDefault['cu_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['la_stats_refresh'] != preferences['la_stats_refresh']) {
+ loadAvgInfoReduce({reset: chartsDefault['la_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['pcu_stats_refresh'] != preferences['pcu_stats_refresh']) {
+ setProcessCpuUsageStats({reset: chartsDefault['pcu_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('cu_stats')){
+ let new_cu_stats = {
+ 'User Normal': data['cu_stats']['usermode_normal_process_percent']?data['cu_stats']['usermode_normal_process_percent']:0,
+ 'User Niced': data['cu_stats']['usermode_niced_process_percent']?data['cu_stats']['usermode_niced_process_percent']:0,
+ 'Kernel': data['cu_stats']['kernelmode_process_percent']?data['cu_stats']['kernelmode_process_percent']:0,
+ 'Idle': data['cu_stats']['idle_mode_percent']?data['cu_stats']['idle_mode_percent']:0,
+ };
+ cpuUsageInfoReduce({incoming: new_cu_stats});
+ }
+
+ if(data.hasOwnProperty('la_stats')){
+ let new_la_stats = {
+ '1 min': data['la_stats']['load_avg_one_minute']?data['la_stats']['load_avg_one_minute']:0,
+ '5 mins': data['la_stats']['load_avg_five_minutes']?data['la_stats']['load_avg_five_minutes']:0,
+ '10 mins': data['la_stats']['load_avg_ten_minutes']?data['la_stats']['load_avg_ten_minutes']:0,
+ '15 mins': data['la_stats']['load_avg_fifteen_minutes']?data['la_stats']['load_avg_fifteen_minutes']:0,
+ };
+ loadAvgInfoReduce({incoming: new_la_stats});
+ }
+
+ if(data.hasOwnProperty('pcu_stats')){
+ let pcu_info_list = [];
+ const pcu_info_obj = data['pcu_stats'];
+ for (const key in pcu_info_obj) {
+ pcu_info_list.push({ icon: '', pid: pcu_info_obj[key]['pid'], name: pcu_info_obj[key]['name'], cpu_usage: formatBytes(pcu_info_obj[key]['cpu_usage']) });
+ }
+
+ setProcessCpuUsageStats(pcu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ cpuUsageInfoReduce({reset:chartsDefault['cu_stats']});
+ loadAvgInfoReduce({reset:chartsDefault['la_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <CPUWrapper
+ cpuUsageInfo={transformData(cpuUsageInfo, preferences['cu_stats_refresh'])}
+ loadAvgInfo={transformData(loadAvgInfo, preferences['la_stats_refresh'])}
+ processCpuUsageStats={processCpuUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+CPU.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function CPUWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('CPU Usage ()')}</div>
+ <ChartContainer id='cu-graph' title={gettext('')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.cpuUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Load Average')}</div>
+ <ChartContainer id='la-graph' title={gettext('')} datasets={props.loadAvgInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.loadAvgInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processCpuUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+CPUWrapper.propTypes = {
+ cpuUsageInfo: propTypeStats.isRequired,
+ loadAvgInfo: propTypeStats.isRequired,
+ processCpuUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
new file mode 100644
index 000000000..70af40919
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
@@ -0,0 +1,372 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function formatBytes(bytes) {
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ let unitIndex = 0;
+
+ while (bytes >= 1024 && unitIndex < units.length - 1) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ return `${bytes.toFixed(2)} ${units[unitIndex]}`;
+}
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'sm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'pmu_stats': {},
+};
+
+export default function Memory({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [memoryUsageInfo, memoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['m_stats']);
+ const [swapMemoryUsageInfo, swapMemoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['sm_stats']);
+ const [processMemoryUsageStats, setProcessMemoryUsageStats] = useState([]);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Usage',
+ accessor: 'memory_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Bytes',
+ accessor: 'memory_bytes',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['m_stats_refresh'] != preferences['m_stats_refresh']) {
+ memoryUsageInfoReduce({reset: chartsDefault['m_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['sm_stats_refresh'] != preferences['sm_stats_refresh']) {
+ swapMemoryUsageInfoReduce({reset: chartsDefault['sm_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['pmu_stats_refresh'] != preferences['pmu_stats_refresh']) {
+ setProcessMemoryUsageStats({reset: chartsDefault['pmu_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('m_stats')){
+ let new_m_stats = {
+ 'Total': data['m_stats']['total_memory']?data['m_stats']['total_memory']:0,
+ 'Used': data['m_stats']['used_memory']?data['m_stats']['used_memory']:0,
+ 'Free': data['m_stats']['free_memory']?data['m_stats']['free_memory']:0,
+ };
+ memoryUsageInfoReduce({incoming: new_m_stats});
+ }
+
+ if(data.hasOwnProperty('sm_stats')){
+ let new_sm_stats = {
+ 'Total': data['sm_stats']['swap_total']?data['sm_stats']['swap_total']:0,
+ 'Used': data['sm_stats']['swap_used']?data['sm_stats']['swap_used']:0,
+ 'Free': data['sm_stats']['swap_free']?data['sm_stats']['swap_free']:0,
+ };
+ swapMemoryUsageInfoReduce({incoming: new_sm_stats});
+ }
+
+ if(data.hasOwnProperty('pmu_stats')){
+ let pmu_info_list = [];
+ const pmu_info_obj = data['pmu_stats'];
+ for (const key in pmu_info_obj) {
+ pmu_info_list.push({ icon: '', pid: pmu_info_obj[key]['pid'], name: pmu_info_obj[key]['name'], memory_usage: formatBytes(pmu_info_obj[key]['memory_usage']), memory_bytes: formatBytes(pmu_info_obj[key]['memory_bytes']) });
+ }
+
+ setProcessMemoryUsageStats(pmu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ memoryUsageInfoReduce({reset:chartsDefault['m_stats']});
+ swapMemoryUsageInfoReduce({reset:chartsDefault['sm_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <MemoryWrapper
+ memoryUsageInfo={transformData(memoryUsageInfo, preferences['m_stats_refresh'])}
+ swapMemoryUsageInfo={transformData(swapMemoryUsageInfo, preferences['sm_stats_refresh'])}
+ processMemoryUsageStats={processMemoryUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Memory.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function MemoryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Memory')}</div>
+ <ChartContainer id='m-graph' title={gettext('')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.memoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Swap Memory')}</div>
+ <ChartContainer id='sm-graph' title={gettext('')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processMemoryUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+MemoryWrapper.propTypes = {
+ memoryUsageInfo: propTypeStats.isRequired,
+ swapMemoryUsageInfo: propTypeStats.isRequired,
+ processMemoryUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
new file mode 100644
index 000000000..50cfa20f4
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
@@ -0,0 +1,329 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ ioDiskContainer: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+ chartHeader: {
+ fontSize: '14px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function ioStatsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(disk_stats => {
+ newState[disk_stats] = {};
+ Object.keys(action.incoming[disk_stats]).forEach(label => {
+ if(state[disk_stats][label]) {
+ newState[disk_stats][label] = [
+ action.counter ? action.incoming[disk_stats][label] - action.counterData[disk_stats][label] : action.incoming[disk_stats][label],
+ ...state[disk_stats][label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[disk_stats][label] = [
+ action.counter ? action.incoming[disk_stats][label] - action.counterData[disk_stats][label] : action.incoming[disk_stats][label],
+ ];
+ }
+ });
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'io_stats': {},
+};
+
+export default function Storage({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [ioInfo, ioInfoReduce] = useReducer(ioStatsReducer, chartsDefault['io_stats']);
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['io_stats_refresh'] != preferences['io_stats_refresh']) {
+ ioInfoReduce({reset: chartsDefault['io_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('io_stats')){
+ const io_info_obj = data['io_stats'];
+ for (const disk in io_info_obj) {
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_total_rw`)){
+ chartsDefault.io_stats[`${disk}_total_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_total_rw`)){
+ ioInfo[`${disk}_total_rw`] = {'Read': [], 'Write': []};
+ }
+
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_bytes_rw`)){
+ chartsDefault.io_stats[`${disk}_bytes_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_bytes_rw`)){
+ ioInfo[`${disk}_bytes_rw`] = {'Read': [], 'Write': []};
+ }
+
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_time_rw`)){
+ chartsDefault.io_stats[`${disk}_time_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_time_rw`)){
+ ioInfo[`${disk}_time_rw`] = {'Read': [], 'Write': []};
+ }
+ }
+
+ let new_io_stats = {};
+ for (const disk in io_info_obj) {
+ new_io_stats[`${disk}_total_rw`] = {'Read': io_info_obj[`${disk}`]['total_reads']?io_info_obj[`${disk}`]['total_reads']:0, 'Write': io_info_obj[`${disk}`]['total_writes']?io_info_obj[`${disk}`]['total_writes']:0};
+ new_io_stats[`${disk}_bytes_rw`] = {'Read': io_info_obj[`${disk}`]['read_bytes']?io_info_obj[`${disk}`]['read_bytes']:0, 'Write': io_info_obj[`${disk}`]['write_bytes']?io_info_obj[`${disk}`]['write_bytes']:0};
+ new_io_stats[`${disk}_time_rw`] = {'Read': io_info_obj[`${disk}`]['read_time_ms']?io_info_obj[`${disk}`]['read_time_ms']:0, 'Write': io_info_obj[`${disk}`]['write_time_ms']?io_info_obj[`${disk}`]['write_time_ms']:0};
+ }
+ ioInfoReduce({incoming: new_io_stats});
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ ioInfoReduce({reset:chartsDefault['io_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <StorageWrapper
+ ioInfo={ioInfo}
+ ioRefreshRate={preferences['io_stats_refresh']}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Storage.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function StorageWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+
+ const keys = Object.keys(props.ioInfo);
+ return (
+ <>
+ {keys.map((key, index) => (
+ index % 3 === 0 && (
+ <Grid key={`disk-${index}`} container spacing={1} className={classes.container}>
+ <Grid container spacing={1} className={classes.ioDiskContainer}>
+ <div className={classes.containerHeader}>{gettext(`Disk ${Math.floor(index / 3) + 1}`)}</div>
+ </Grid>
+ <Grid container spacing={1} className={classes.ioDiskContainer}>
+ {keys.slice(index, index + 3).map((innerKey, innerKeyIndex) => (
+ <Grid key={`${innerKey}`} item md={4} sm={6}>
+ <div className={classes.chartHeader}>{innerKeyIndex==0 ? gettext('I/O Operations Count'): innerKeyIndex==1? gettext('Data Transfer (Bytes)'):gettext('Time Spent in I/O Operations (Milliseconds)')}</div>
+ <ChartContainer id={`io-graph-${innerKey}`} title={gettext('')} datasets={transformData(props.ioInfo[innerKey], props.ioRefreshRate).datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={transformData(props.ioInfo[innerKey], props.ioRefreshRate)} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ ))}
+ </Grid>
+ </Grid>
+ )
+ ))}
+ </>
+ );
+}
+
+StorageWrapper.propTypes = {
+ ioInfo: PropTypes.objectOf(
+ PropTypes.shape({
+ Read: PropTypes.array,
+ Write: PropTypes.array,
+ })
+ ),
+ ioRefreshRate: PropTypes.number.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
new file mode 100644
index 000000000..f3e23e2b0
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
@@ -0,0 +1,422 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import DonutChart from '../../../../static/js/components/PgChart/DonutChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ table: {
+ width: '100%',
+ backgroundColor: theme.otherVars.tableBg,
+ border: '1px solid rgb(221, 224, 230)',
+ },
+ tableVal: {
+ border: '1px solid rgb(221, 224, 230) !important',
+ padding: '10px !important',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'hpc_stats': {'Handle': new Array(X_AXIS_LENGTH).fill(null), 'Process': new Array(X_AXIS_LENGTH).fill(null)},
+};
+
+const SummaryTable = (props) => {
+ const classes = useStyles();
+ const data = props.data;
+ return (
+ <table className={classes.table}>
+ <thead>
+ <tr>
+ <th className={classes.tableVal}>Property</th>
+ <th className={classes.tableVal}>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {data.map((item, index) => (
+ <tr className={classes.tableVal} key={index}>
+ <td className={classes.tableVal}>{item.name}</td>
+ <td className={classes.tableVal}>{item.value}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+};
+
+SummaryTable.propTypes = {
+ data: PropTypes.any,
+};
+
+export default function Summary({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [processHandleCount, processHandleCountReduce] = useReducer(statsReducer, chartsDefault['hpc_stats']);
+ const [osStats, setOsStats] = useState([]);
+ const [cpuStats, setCpuStats] = useState([]);
+ const [processInfoStats] = useState({'Running': 4, 'Sleeping': 2, 'Stopped': 1, 'Zombie': 2});
+
+ const [counterData, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [longPollDelay] = useState(180000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'Property',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Value',
+ accessor: 'value',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['hpc_stats_refresh'] != preferences['hpc_stats_refresh']) {
+ processHandleCountReduce({reset: chartsDefault['hpc_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useEffect(() => {
+ try {
+ // Fetch the latest data point from the API endpoint
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'pg_sys_os_info,pg_sys_cpu_info';
+ const api = getApiInstance();
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ let data = res.data;
+
+ const os_info_obj = data['pg_sys_os_info'];
+ let os_info_list = [
+ { icon: '', name: 'Name', value: os_info_obj['name'] },
+ { icon: '', name: 'Version', value: os_info_obj['version'] },
+ { icon: '', name: 'Host name', value: os_info_obj['host_name'] },
+ { icon: '', name: 'Domain name', value: os_info_obj['domain_name'] },
+ { icon: '', name: 'Architecture', value: os_info_obj['architecture'] },
+ { icon: '', name: 'Os up since seconds', value: os_info_obj['os_up_since_seconds'] },
+ ];
+ setOsStats(os_info_list);
+
+ const cpu_info_obj = data['pg_sys_cpu_info'];
+ let cpu_info_list = [
+ { icon: '', name: 'Vendor', value: cpu_info_obj['vendor'] },
+ { icon: '', name: 'Description', value: cpu_info_obj['description'] },
+ { icon: '', name: 'Model name', value: cpu_info_obj['model_name'] },
+ { icon: '', name: 'No of cores', value: cpu_info_obj['no_of_cores'] },
+ { icon: '', name: 'Architecture', value: cpu_info_obj['architecture'] },
+ { icon: '', name: 'Clock speed Hz', value: cpu_info_obj['clock_speed_hz'] },
+ { icon: '', name: 'L1 dcache size', value: cpu_info_obj['l1dcache_size'] },
+ { icon: '', name: 'L1 icache size', value: cpu_info_obj['l1icache_size'] },
+ { icon: '', name: 'L2 cache size', value: cpu_info_obj['l2cache_size'] },
+ { icon: '', name: 'L3 cache size', value: cpu_info_obj['l3cache_size'] },
+ ];
+ setCpuStats(cpu_info_list);
+
+ setErrorMsg(null);
+ })
+ .catch((error) => {
+ console.error('Error fetching data:', error);
+ });
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ }, [sid, did, enablePoll, pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ processHandleCountReduce({incoming: data['hpc_stats']});
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ processHandleCountReduce({reset:chartsDefault['hpc_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ useInterval(()=>{
+ // let url;
+ // url = url_for('dashboard.system_statistics');
+ // url += '/' + sid;
+ // url += did > 0 ? '/' + did : '';
+ // url += '?chart_names=' + 'pi_stats';
+ // axios.get(url)
+ // .then((resp)=>{
+ // let data = resp.data;
+ // console.log("pi data: ", data);
+ // })
+ // .catch((error)=>{
+ // if(!errorMsg) {
+ // if(error.response) {
+ // if (error.response.status === 428) {
+ // setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ // } else {
+ // setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ // }
+ // } else if(error.request) {
+ // setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ // return;
+ // } else {
+ // console.error(error);
+ // }
+ // }
+ // });
+ }, enablePoll ? longPollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <SummaryWrapper
+ processHandleCount={transformData(processHandleCount, preferences['hpc_stats_refresh'])}
+ osStats={osStats}
+ cpuStats={cpuStats}
+ processInfoStats={transformData(processInfoStats, 5)}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Summary.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function SummaryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('OS Information')}</div>
+ <SummaryTable data={props.osStats} />
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Handle & Process Count')}</div>
+ <ChartContainer id='hpc-graph' title={gettext('')} datasets={props.processHandleCount.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.processHandleCount} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} showSecondAxis={true} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('CPU Information')}</div>
+ <SummaryTable data={props.cpuStats} />
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Process Information')}</div>
+ <ChartContainer id='pi-graph' title={gettext('')} datasets={props.processInfoStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <DonutChart data={props.processInfoStats.datasets} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ </>
+ );
+}
+
+SummaryWrapper.propTypes = {
+ processHandleCount: PropTypes.any.isRequired,
+ osStats: PropTypes.any.isRequired,
+ cpuStats: PropTypes.any.isRequired,
+ processInfoStats: PropTypes.any.isRequired,
+ tableHeader: PropTypes.any.isRequired,
+ errorMsg: PropTypes.any,
+ showTooltip: PropTypes.bool,
+ showDataPoints: PropTypes.bool,
+ lineBorderWidth: PropTypes.number,
+ isDatabase: PropTypes.bool,
+ isTest: PropTypes.bool,
+};
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
new file mode 100644
index 000000000..9024a2c5e
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
@@ -0,0 +1,100 @@
+{% set add_union = false %}
+{% if 'pg_sys_os_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_os_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_os_info()) t
+{% endif %}
+{% if add_union and 'pg_sys_cpu_info' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pg_sys_cpu_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_cpu_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_info()) t
+{% endif %}
+{% if add_union and 'hpc_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'hpc_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'hpc_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT
+ (SELECT handle_count FROM pg_sys_os_info()) AS "{{ _('Handle') }}",
+ (SELECT process_count FROM pg_sys_os_info()) AS "{{ _('Process') }}"
+ ) t
+{% endif %}
+{% if add_union and 'cu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'cu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'cu_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_usage_info()) t
+{% endif %}
+{% if add_union and 'la_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'la_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'la_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_load_avg_info()) t
+{% endif %}
+{% if add_union and 'pcu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pcu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pcu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, cpu_usage, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT total_memory, used_memory, free_memory FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'sm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'sm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'sm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT swap_total, swap_used, swap_free FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'pmu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pmu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pmu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, memory_usage, memory_bytes, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'io_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'io_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'io_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('disk'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT *, ROW_NUMBER() OVER (ORDER BY device_name) AS row_number
+ FROM pg_sys_io_analysis_info()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'pi_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pi_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pi_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_process_info()) t
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/DonutChart.jsx b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
new file mode 100644
index 000000000..4da1d4435
--- /dev/null
+++ b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
@@ -0,0 +1,70 @@
+import React, { useEffect, useRef } from 'react';
+import Chart from 'chart.js/auto';
+import PropTypes from 'prop-types';
+
+export default function DonutChart({ data }) {
+ const chartRef = useRef(null);
+ const chartInstance = useRef(null);
+
+ useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ if (chartInstance.current) {
+ // If chart instance exists, update the data
+ chartInstance.current.data.labels = data.map((item) => item.label);
+ chartInstance.current.data.datasets[0].data = data.map((item) => item.data);
+ chartInstance.current.update();
+ } else {
+ // If chart instance doesn't exist, create a new chart
+ const chartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: false, // Hide the labels at the top
+ },
+ },
+ animation: {
+ duration: 0, // Disable the animation
+ },
+ tooltips: {
+ callbacks: {
+ label: function (tooltipItem, chartData) {
+ const dataset = chartData.datasets[tooltipItem.datasetIndex];
+ const total = dataset.data.reduce((previousValue, currentValue) => previousValue + currentValue);
+ const currentValue = dataset.data[tooltipItem.index];
+ const percentage = ((currentValue / total) * 100).toFixed(2) + '%';
+ return dataset.label + ': ' + currentValue + ' (' + percentage + ')';
+ },
+ },
+ },
+ };
+
+ const chartData = {
+ labels: data.map((item) => item.label),
+ datasets: [
+ {
+ data: data.map((item) => item.data),
+ backgroundColor: data.map((item) => item.borderColor),
+ hoverBackgroundColor: data.map((item) => item.borderColor),
+ },
+ ],
+ };
+
+ const ctx = chartRef.current.getContext('2d');
+ chartInstance.current = new Chart(ctx, {
+ type: 'doughnut',
+ data: chartData,
+ options: chartOptions,
+ });
+ }
+ }
+ }, [data]);
+
+ return (
+ <canvas ref={chartRef} />
+ );
+}
+
+DonutChart.propTypes = {
+ data: PropTypes.array.isRequired,
+};
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
index bd465e3da..5ccfe3464 100644
--- a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
+++ b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
@@ -32,7 +32,7 @@ function tooltipPlugin(refreshRate) {
showTooltip();
let tooltipHtml=`<div>${(u.data[1].length-1-parseInt(u.legend.values[0]['_'])) * refreshRate + gettext(' seconds ago')}</div>`;
for(let i=1; i<u.series.length; i++) {
- tooltipHtml += `<div class="uplot-tooltip-label"><div style="height:12px; width:12px; background-color:${u.series[i].stroke()}"></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
+ tooltipHtml += `<div class='uplot-tooltip-label'><div style='height:12px; width:12px; background-color:${u.series[i].stroke()}'></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
}
tooltip.innerHTML = tooltipHtml;
@@ -58,44 +58,32 @@ function tooltipPlugin(refreshRate) {
};
}
-export default function StreamingChart({xRange=75, data, options}) {
+export default function StreamingChart({xRange=75, data, options, showSecondAxis=false}) {
const chartRef = useRef();
const theme = useTheme();
const { width, height, ref:containerRef } = useResizeDetector();
- const defaultOptions = useMemo(()=>({
- title: '',
- width: width,
- height: height,
- padding: [10, 0, 10, 0],
- focus: {
- alpha: 0.3,
- },
- cursor: {
- y: false,
- drag: {
- setScale: false,
- }
- },
- series: [
+ const defaultOptions = useMemo(()=> {
+ const series = [
{},
- ...(data.datasets?.map((datum)=>({
+ ...(data.datasets?.map((datum, index) => ({
label: datum.label,
stroke: datum.borderColor,
width: options.lineBorderWidth ?? 1,
- points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius*2 }
- }))??{})
- ],
- scales: {
- x: {
- time: false,
- }
- },
- axes: [
+ scale: showSecondAxis && (index === 1) ? 'y1' : 'y',
+ points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius * 2 },
+ })) ?? []),
+ ];
+
+ const axes = [
{
show: false,
stroke: theme.palette.text.primary,
},
- {
+ ];
+
+ if(showSecondAxis){
+ axes.push({
+ scale: 'y',
grid: {
stroke: theme.otherVars.borderColor,
width: 0.5,
@@ -108,11 +96,104 @@ export default function StreamingChart({xRange=75, data, options}) {
if(size < 40) size = 40;
}
return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ axes.push({
+ scale: 'y1',
+ side: 1,
+ stroke: theme.palette.text.primary,
+ grid: {show: false},
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
}
- }
- ],
- plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
- }), [data.refreshRate, data?.datasets?.length, width, height, options]);
+ });
+ } else{
+ axes.push({
+ scale: 'y',
+ grid: {
+ stroke: theme.otherVars.borderColor,
+ width: 0.5,
+ },
+ stroke: theme.palette.text.primary,
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ }
+
+ return {
+ title: '',
+ width: width,
+ height: height,
+ padding: [10, 0, 10, 0],
+ focus: {
+ alpha: 0.3,
+ },
+ cursor: {
+ y: false,
+ drag: {
+ setScale: false,
+ }
+ },
+ series: series,
+ scales: {
+ x: {
+ time: false,
+ }
+ },
+ axes: axes,
+ plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
+ };
+ }, [data.refreshRate, data?.datasets?.length, width, height, options]);
const initialState = [
Array.from(new Array(xRange).keys()),
@@ -140,4 +221,5 @@ StreamingChart.propTypes = {
xRange: PropTypes.number.isRequired,
data: propTypeData.isRequired,
options: PropTypes.object,
+ showSecondAxis: PropTypes.bool,
};
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-16 05:43 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 0 replies; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-16 05:43 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
On Wed, Aug 16, 2023 at 11:09 AM Sahil Harpal <[email protected]>
wrote:
> Could you please try this attached patch.
>
Still failing.
[image: Screenshot 2023-08-16 at 11.11.58 AM.png]
>
Attachments:
[image/png] Screenshot 2023-08-16 at 11.11.58 AM.png (743.1K, 3-Screenshot%202023-08-16%20at%2011.11.58%20AM.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-16 06:21 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-16 06:21 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: Aditya Toshniwal <[email protected]>; pgadmin-hackers; Ashesh Vashi <[email protected]>; Dave Page <[email protected]>; Akshay Joshi <[email protected]>
I've noticed something here. I cleared all the errors using 'yarn run
bundle:dev,' except for the unused 'counterData' variable, which I'll
correct soon. But when I attempted to apply this patch on a freshly cloned
repository, it displayed the following warnings. However, it didn't fail,
and all the files and folders were created successfully.
[image: image.png]
I removed all the whitespaces before creating the patch, and I have also
checked all these lines where it is showing the trailing whitespace, but
there are no trailing whitespaces. The 'yarn run bundle:dev' command also
does not give any prompt for this. Could you please suggest what to do?
On Wed, 16 Aug 2023 at 11:08, Sahil Harpal <[email protected]>
wrote:
> Could you please try this attached patch.
>
Attachments:
[image/png] image.png (24.4K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-16 09:55 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-16 09:55 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
I have pushed the recent changes to my GitHub repository.
If you are still unable to apply the patch, you can pull it from here. In
the meantime, I'll check what might be wrong with this patch file.
link - https://github.com/Sahil1479/pgadmin4/tree/system_stats
On Wed, 16 Aug 2023 at 11:51, Sahil Harpal <[email protected]>
wrote:
> I've noticed something here. I cleared all the errors using 'yarn run
> bundle:dev,' except for the unused 'counterData' variable, which I'll
> correct soon. But when I attempted to apply this patch on a freshly cloned
> repository, it displayed the following warnings. However, it didn't fail,
> and all the files and folders were created successfully.
> [image: image.png]
> I removed all the whitespaces before creating the patch, and I have also
> checked all these lines where it is showing the trailing whitespace, but
> there are no trailing whitespaces. The 'yarn run bundle:dev' command also
> does not give any prompt for this. Could you please suggest what to do?
>
>
> On Wed, 16 Aug 2023 at 11:08, Sahil Harpal <[email protected]>
> wrote:
>
>> Could you please try this attached patch.
>>
>
Attachments:
[image/png] image.png (24.4K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 05:09 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-17 05:09 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
Hi Sahil,
On Wed, Aug 16, 2023 at 3:25 PM Sahil Harpal <[email protected]>
wrote:
> I have pushed the recent changes to my GitHub repository.
> If you are still unable to apply the patch, you can pull it from here. In
> the meantime, I'll check what might be wrong with this patch file.
> link - https://github.com/Sahil1479/pgadmin4/tree/system_stats
>
Can you please fix the below JS error ? Due to this CPU and Memory tabs are
not working.
[image: Screenshot 2023-08-17 at 10.35.34 AM.png]
Thanks,
Khushboo
>
> On Wed, 16 Aug 2023 at 11:51, Sahil Harpal <[email protected]>
> wrote:
>
>> I've noticed something here. I cleared all the errors using 'yarn run
>> bundle:dev,' except for the unused 'counterData' variable, which I'll
>> correct soon. But when I attempted to apply this patch on a freshly cloned
>> repository, it displayed the following warnings. However, it didn't fail,
>> and all the files and folders were created successfully.
>> [image: image.png]
>> I removed all the whitespaces before creating the patch, and I have also
>> checked all these lines where it is showing the trailing whitespace, but
>> there are no trailing whitespaces. The 'yarn run bundle:dev' command also
>> does not give any prompt for this. Could you please suggest what to do?
>>
>>
>> On Wed, 16 Aug 2023 at 11:08, Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Could you please try this attached patch.
>>>
>>
Attachments:
[image/png] image.png (24.4K, 3-image.png)
download | view image
[image/png] Screenshot 2023-08-17 at 10.35.34 AM.png (31.1K, 4-Screenshot%202023-08-17%20at%2010.35.34%20AM.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 06:18 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-17 06:18 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
Hi Khushboo,
On Thu, 17 Aug 2023 at 10:39, Khushboo Vashi <
[email protected]> wrote:
> Can you please fix the below JS error ? Due to this CPU and Memory tabs
> are not working.
> [image: Screenshot 2023-08-17 at 10.35.34 AM.png]
>
For me, it's working properly. I think in your case, the system is
returning a null value for some of the processes. Could you please confirm
this? If it is so, then either we can set a default value of 0 or simply a
null string. Using console.log(), you can see what it is returning.
if(data.hasOwnProperty('pmu_stats')){
let pmu_info_list = [];
const pmu_info_obj = data['pmu_stats'];
console.log(pmu_info_obj); // It will print entire list in the console
for (const key in pmu_info_obj) {
pmu_info_list.push({ icon: '', pid: pmu_info_obj[key]['pid'], name:
pmu_info_obj[key]['name'],
memory_usage: formatBytes(pmu_info_obj[key]['memory_usage']), memory_bytes:
formatBytes(pmu_info_obj[key]['memory_bytes']) });
}
setProcessMemoryUsageStats(pmu_info_list);
}[image: image.gif]
Attachments:
[image/gif] image.gif (42B, 3-image.gif)
download | view image
[image/png] Screenshot 2023-08-17 at 10.35.34 AM.png (31.1K, 4-Screenshot%202023-08-17%20at%2010.35.34%20AM.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 09:09 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-17 09:09 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
On Thu, Aug 17, 2023 at 11:48 AM Sahil Harpal <[email protected]>
wrote:
> Hi Khushboo,
>
> On Thu, 17 Aug 2023 at 10:39, Khushboo Vashi <
> [email protected]> wrote:
>
>> Can you please fix the below JS error ? Due to this CPU and Memory tabs
>> are not working.
>> [image: Screenshot 2023-08-17 at 10.35.34 AM.png]
>>
>
> For me, it's working properly. I think in your case, the system is
> returning a null value for some of the processes. Could you please confirm
> this? If it is so, then either we can set a default value of 0 or simply a
> null string. Using console.log(), you can see what it is returning.
>
> Sample data of my system to fix this issue:
1. process331:
1. memory_bytes: null
2. memory_usage: null
3. name: "QuickLookUIServi"
4. pid: 938
5. row_number: 331
> if(data.hasOwnProperty('pmu_stats')){
> let pmu_info_list = [];
> const pmu_info_obj = data['pmu_stats'];
> console.log(pmu_info_obj); // It will print entire list in the console
> for (const key in pmu_info_obj) {
> pmu_info_list.push({ icon: '', pid: pmu_info_obj[key]['pid'], name:
> pmu_info_obj[key]['name'],
> memory_usage: formatBytes(pmu_info_obj[key]['memory_usage']), memory_bytes
> : formatBytes(pmu_info_obj[key]['memory_bytes']) });
> }
> setProcessMemoryUsageStats(pmu_info_list);
> }[image: image.gif]
>
Attachments:
[image/gif] image.gif (42B, 3-image.gif)
download | view image
[image/png] Screenshot 2023-08-17 at 10.35.34 AM.png (31.1K, 4-Screenshot%202023-08-17%20at%2010.35.34%20AM.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 09:20 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-17 09:20 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
On Thu, 17 Aug 2023 at 14:39, Khushboo Vashi <
[email protected]> wrote:
> Sample data of my system to fix this issue:
>
> 1. process331:
> 1. memory_bytes:null
> 2. memory_usage:null
> 3. name:"QuickLookUIServi"
> 4. pid:938
> 5. row_number:331
>
> Okay, so it is an issue of null value then.
Are null values being returned for all processes or only for specific ones?
Also can you please verify it by running "SELECT * FROM
pg_sys_cpu_memory_by_process();" query in the query tool? Just want to
ensure I'm not making mistakes while processing the queries output.
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 09:23 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-17 09:23 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
On Thu, Aug 17, 2023 at 2:50 PM Sahil Harpal <[email protected]>
wrote:
> On Thu, 17 Aug 2023 at 14:39, Khushboo Vashi <
> [email protected]> wrote:
>
>> Sample data of my system to fix this issue:
>>
>> 1. process331:
>> 1. memory_bytes:null
>> 2. memory_usage:null
>> 3. name:"QuickLookUIServi"
>> 4. pid:938
>> 5. row_number:331
>>
>> Okay, so it is an issue of null value then.
> Are null values being returned for all processes or only for specific ones?
>
It returns null for some of the processes, not for all.
Also can you please verify it by running "SELECT * FROM
> pg_sys_cpu_memory_by_process();" query in the query tool? Just want to
> ensure I'm not making mistakes while processing the queries output.
>
>>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 09:35 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-17 09:35 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
On Thu, 17 Aug 2023 at 14:53, Khushboo Vashi <
[email protected]> wrote:
> On Thu, Aug 17, 2023 at 2:50 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Thu, 17 Aug 2023 at 14:39, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>> Sample data of my system to fix this issue:
>>>
>>> 1. process331:
>>> 1. memory_bytes:null
>>> 2. memory_usage:null
>>> 3. name:"QuickLookUIServi"
>>> 4. pid:938
>>> 5. row_number:331
>>>
>>> Okay, so it is an issue of null value then.
>> Are null values being returned for all processes or only for specific
>> ones?
>>
> It returns null for some of the processes, not for all.
>
Okay, great. So should we display null for those processes or 0B?
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 09:38 Dave Page <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Dave Page @ 2023-08-17 09:38 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers
On Thu, 17 Aug 2023 at 10:36, Sahil Harpal <[email protected]>
wrote:
> On Thu, 17 Aug 2023 at 14:53, Khushboo Vashi <
> [email protected]> wrote:
>
>> On Thu, Aug 17, 2023 at 2:50 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> On Thu, 17 Aug 2023 at 14:39, Khushboo Vashi <
>>> [email protected]> wrote:
>>>
>>>> Sample data of my system to fix this issue:
>>>>
>>>> 1. process331:
>>>> 1. memory_bytes:null
>>>> 2. memory_usage:null
>>>> 3. name:"QuickLookUIServi"
>>>> 4. pid:938
>>>> 5. row_number:331
>>>>
>>>> Okay, so it is an issue of null value then.
>>> Are null values being returned for all processes or only for specific
>>> ones?
>>>
>> It returns null for some of the processes, not for all.
>>
>
> Okay, great. So should we display null for those processes or 0B?
>
null. We don't have any info, which is not the same as 0B.
--
Dave Page
Blog: https://pgsnake.blogspot.com
Twitter: @pgsnake
EDB: https://www.enterprisedb.com
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 09:47 Sahil Harpal <[email protected]>
parent: Dave Page <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-17 09:47 UTC (permalink / raw)
To: Dave Page <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers
On Thu, 17 Aug 2023 at 15:08, Dave Page <[email protected]> wrote:
> On Thu, 17 Aug 2023 at 10:36, Sahil Harpal <[email protected]>
> wrote:
>
>> On Thu, 17 Aug 2023 at 14:53, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>> On Thu, Aug 17, 2023 at 2:50 PM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> [image: image.gif]
>>>> On Thu, 17 Aug 2023 at 14:39, Khushboo Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>> Sample data of my system to fix this issue:
>>>>>
>>>>> 1. process331:
>>>>> 1. memory_bytes:null
>>>>> 2. memory_usage:null
>>>>> 3. name:"QuickLookUIServi"
>>>>> 4. pid:938
>>>>> 5. row_number:331
>>>>>
>>>>> Okay, so it is an issue of null value then.
>>>> Are null values being returned for all processes or only for specific
>>>> ones?
>>>>
>>> It returns null for some of the processes, not for all.
>>>
>>
>> Okay, great. So should we display null for those processes or 0B?
>>
>
> null. We don't have any info, which is not the same as 0B.
>
Understood. Thank you!
Attachments:
[image/gif] image.gif (42B, 3-image.gif)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 10:12 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-17 10:12 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
Hi Khushboo,
I have made the necessary changes and pushed them to the same
repository. Let me know if that resolves this null value issue.
Thank you,
Sahil
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-17 12:27 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-17 12:27 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
Hi Sahil,
Please find the below review comments:
- Do not use the Title case for the graph/chart’s heading. For example, *CPU
Usage *should be *CPU usage*.
- If I select any database without an extension installed and then
select the database with the extension, the message (Extension is not
installed) remains on the page. The database change doesn’t reflect on the
UI.
- CPU Usage () - What are these empty parentheses?
- Please use the same grey background shades on the entire page.
- Storage tab: If possible, please display the disk name in the heading
- The Tooltip for graph points remains on the page. Please refer to the
below screenshot.
[image: Screenshot 2023-08-17 at 5.50.19 PM.png]
- Please use rounded corners for all the tables (same as graphs).
- Sometimes graphs start from left and then shift to right. This issue I
have reported in my initial review too.
Thanks,
Khushboo
On Thu, Aug 17, 2023 at 3:43 PM Sahil Harpal <[email protected]>
wrote:
> Hi Khushboo,
>
> I have made the necessary changes and pushed them to the same
> repository. Let me know if that resolves this null value issue.
>
> Thank you,
> Sahil
>
>>
Attachments:
[image/png] Screenshot 2023-08-17 at 5.50.19 PM.png (190.0K, 3-Screenshot%202023-08-17%20at%205.50.19%20PM.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-18 04:38 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
1 sibling, 2 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-08-18 04:38 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
[email protected]> wrote:
>
> - The Tooltip for graph points remains on the page. Please refer to
> the below screenshot.
>
>
Is this happening simply by moving the cursor within the graph area or when
resizing the screen?
For me, it's working fine if I move the cursor within the graph area, but I
can also sometimes observe this glitch when I attempt to change the screen
width and quickly move the cursor within the graph area.
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-19 10:50 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-19 10:50 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers; Aditya Toshniwal <[email protected]>
On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
[email protected]> wrote:
>
> - Sometimes graphs start from left and then shift to right. This issue
> I have reported in my initial review too.
>
> For StreamingChart should I ignore it for now?
On Mon, 7 Aug 2023 at 17:12, Aditya Toshniwal <
[email protected]> wrote:
> OK I thought you wanted the graph to go from left to right. Yeah, I have
> seen that glitch a few times (but rare). I think you can ignore it for now,
> we can look into it once we get time. It is not a priority.
>
Or try the solution below?
On Sun, 6 Aug 2023 at 13:21, Sahil Harpal <[email protected]> wrote:
> I've tried one more approach here. If our data array is full, this graph
> shifting won't happen. So, if we initialize our data list with null values,
> there won't be any visible shift. What do you think about this solution?
>
Thank you,
Sahil
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-19 10:51 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-19 10:51 UTC (permalink / raw)
To: pgadmin-hackers; +Cc: Khushboo Vashi <[email protected]>; Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
wrote:
> Pending Work:
>
> 1. Process information -
> - Issue: The pg_sys_process_info() query takes much longer (more
> than 2 mins) to execute and prevents the updation of other graphs and
> tables.
> 2. Disk information -
> - Issue: The pg_sys_disk_info() query returns NULL value for some
> of the drive letters.
> 3. StreamingChart -
> - Issue: Graph shifting glitch. For the first time, graphs start
> from the opposite side, and after the following API call or a few seconds
> later, those get adjusted.
>
> I need suggestions for labels for different tables and charts. Also, could
> you please clarify the use of the counterData variable, which is used for
> some of the charts (tps_stats, ti_stats, to_stats, and bio_stats)?
>
Could you please help me with these blockers and doubts?
Thank you,
Sahil
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-21 04:25 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-21 04:25 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
On Fri, Aug 18, 2023 at 10:08 AM Sahil Harpal <[email protected]>
wrote:
> On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
> [email protected]> wrote:
>
>>
>> - The Tooltip for graph points remains on the page. Please refer to
>> the below screenshot.
>>
>>
> Is this happening simply by moving the cursor within the graph area or
> when resizing the screen?
>
This is happening by simply moving the cursor across the graphs on each
tab. Maybe you can try to reduce the graph refresh rate to 1 second and try.
>
> For me, it's working fine if I move the cursor within the graph area, but
> I can also sometimes observe this glitch when I attempt to change the
> screen width and quickly move the cursor within the graph area.
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-21 04:29 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 2 replies; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-21 04:29 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
On Sat, Aug 19, 2023 at 4:21 PM Sahil Harpal <[email protected]>
wrote:
> On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
> wrote:
>
>> Pending Work:
>>
>> 1. Process information -
>> - Issue: The pg_sys_process_info() query takes much longer (more
>> than 2 mins) to execute and prevents the updation of other graphs and
>> tables.
>>
>> This may happen on Windows, can you try to fetch these details separately
(not combining with other charts) only for Windows?
>
>> 1. Disk information -
>> - Issue: The pg_sys_disk_info() query returns NULL value for some
>> of the drive letters.
>>
>> If it returns NULL then do not show anything for that field for
particular Disk.
>
>> 1. StreamingChart -
>> - Issue: Graph shifting glitch. For the first time, graphs start
>> from the opposite side, and after the following API call or a few seconds
>> later, those get adjusted.
>>
>> I need suggestions for labels for different tables and charts. Also,
>> could you please clarify the use of the counterData variable, which is used
>> for some of the charts (tps_stats, ti_stats, to_stats, and bio_stats)?
>>
>
> Could you please help me with these blockers and doubts?
>
> Thank you,
> Sahil
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-21 04:32 Khushboo Vashi <[email protected]>
parent: Khushboo Vashi <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-21 04:32 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
Sahil, once the issues get resolved, please raise the PR and we will do the
final review there.
On Mon, Aug 21, 2023 at 9:59 AM Khushboo Vashi <
[email protected]> wrote:
>
>
> On Sat, Aug 19, 2023 at 4:21 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Pending Work:
>>>
>>> 1. Process information -
>>> - Issue: The pg_sys_process_info() query takes much longer (more
>>> than 2 mins) to execute and prevents the updation of other graphs and
>>> tables.
>>>
>>> This may happen on Windows, can you try to fetch these details
> separately (not combining with other charts) only for Windows?
>
>>
>>> 1. Disk information -
>>> - Issue: The pg_sys_disk_info() query returns NULL value for some
>>> of the drive letters.
>>>
>>> If it returns NULL then do not show anything for that field for
> particular Disk.
>
>>
>>> 1. StreamingChart -
>>> - Issue: Graph shifting glitch. For the first time, graphs start
>>> from the opposite side, and after the following API call or a few seconds
>>> later, those get adjusted.
>>>
>>> I need suggestions for labels for different tables and charts. Also,
>>> could you please clarify the use of the counterData variable, which is used
>>> for some of the charts (tps_stats, ti_stats, to_stats, and bio_stats)?
>>>
>>
>> Could you please help me with these blockers and doubts?
>>
>> Thank you,
>> Sahil
>>
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-21 05:21 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-21 05:21 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
pgAdmin4-cursor-rec.mp4
<https://drive.google.com/file/d/19_Fw-hvZue4_1tTED9INss33eZcfdIEd/view?usp=drive_web;
Hi Khushboo,
On Mon, 21 Aug 2023 at 09:55, Khushboo Vashi <
[email protected]> wrote:
>
> On Fri, Aug 18, 2023 at 10:08 AM Sahil Harpal <[email protected]>
> wrote:
>
>> On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>>
>>> - The Tooltip for graph points remains on the page. Please refer to
>>> the below screenshot.
>>>
>>> Is this happening simply by moving the cursor within the graph area or
>> when resizing the screen?
>>
> This is happening by simply moving the cursor across the graphs on each
> tab. Maybe you can try to reduce the graph refresh rate to 1 second and try.
>
I'm unable to reproduce the issue even after modifying the refresh rate to
1 second. I have attached a screen recording to illustrate how it is
functioning on my end.
Do you have any thoughts on why the cursor issue might be happening?
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-22 07:48 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-22 07:48 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
Hello Khushboo,
Thank you for sharing this with me. As you can see, towards the end, when
you encountered the tooltip issue, the size of the graph container was
changing. I even found this bug in the actual pgAdmin4 application
installed on my system. Please see the attached video.
Here's what I believe is happening: When the container's size changes, it
seems that tooltips continue to accumulate without being properly removed.
The method we use to display tooltips involves inserting a new element into
the DOM with the class name "uplot-tooltip". So, if we remove all the
existing tooltip elements before inserting a new one, it could potentially
resolve the issue. Would like to know your thoughts on this.
Thank you,
Sahil
On Mon, 21 Aug 2023 at 11:11, Khushboo Vashi <
[email protected]> wrote:
> See the attachment.
>
> On Mon, Aug 21, 2023 at 10:51 AM Sahil Harpal <[email protected]>
> wrote:
>
>> pgAdmin4-cursor-rec.mp4
>> <https://drive.google.com/file/d/19_Fw-hvZue4_1tTED9INss33eZcfdIEd/view?usp=drive_web;
>> Hi Khushboo,
>> On Mon, 21 Aug 2023 at 09:55, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>>
>>> On Fri, Aug 18, 2023 at 10:08 AM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>>
>>>>> - The Tooltip for graph points remains on the page. Please refer
>>>>> to the below screenshot.
>>>>>
>>>>> Is this happening simply by moving the cursor within the graph area or
>>>> when resizing the screen?
>>>>
>>> This is happening by simply moving the cursor across the graphs on each
>>> tab. Maybe you can try to reduce the graph refresh rate to 1 second and try.
>>>
>>
>> I'm unable to reproduce the issue even after modifying the refresh rate
>> to 1 second. I have attached a screen recording to illustrate how it is
>> functioning on my end.
>> Do you have any thoughts on why the cursor issue might be happening?
>>
>
Attachments:
[video/mp4] pgAdmin4-tooltip-on-resizing-window.mp4 (4.4M, 3-pgAdmin4-tooltip-on-resizing-window.mp4)
download
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-22 10:32 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-22 10:32 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
Hi Sahil,
On Tue, Aug 22, 2023 at 1:19 PM Sahil Harpal <[email protected]>
wrote:
> Hello Khushboo,
>
> Thank you for sharing this with me. As you can see, towards the end, when
> you encountered the tooltip issue, the size of the graph container was
> changing. I even found this bug in the actual pgAdmin4 application
> installed on my system. Please see the attached video.
>
> Here's what I believe is happening: When the container's size changes, it
> seems that tooltips continue to accumulate without being properly removed.
> The method we use to display tooltips involves inserting a new element
> into the DOM with the class name "uplot-tooltip". So, if we remove all the
> existing tooltip elements before inserting a new one, it could potentially
> resolve the issue. Would like to know your thoughts on this.
>
> It would be good if you fix this issue as you already investigated and
found the root cause. But if you have other things to complete as the
deadline is approaching you can create this issue at pgAdmin repo as it is
an existing one.
Thanks,
Khushboo
> Thank you,
> Sahil
>
> On Mon, 21 Aug 2023 at 11:11, Khushboo Vashi <
> [email protected]> wrote:
>
>> See the attachment.
>>
>> On Mon, Aug 21, 2023 at 10:51 AM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> pgAdmin4-cursor-rec.mp4
>>> <https://drive.google.com/file/d/19_Fw-hvZue4_1tTED9INss33eZcfdIEd/view?usp=drive_web;
>>> Hi Khushboo,
>>> On Mon, 21 Aug 2023 at 09:55, Khushboo Vashi <
>>> [email protected]> wrote:
>>>
>>>>
>>>> On Fri, Aug 18, 2023 at 10:08 AM Sahil Harpal <
>>>> [email protected]> wrote:
>>>>
>>>>> On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>>
>>>>>> - The Tooltip for graph points remains on the page. Please refer
>>>>>> to the below screenshot.
>>>>>>
>>>>>> Is this happening simply by moving the cursor within the graph area
>>>>> or when resizing the screen?
>>>>>
>>>> This is happening by simply moving the cursor across the graphs on each
>>>> tab. Maybe you can try to reduce the graph refresh rate to 1 second and try.
>>>>
>>>
>>> I'm unable to reproduce the issue even after modifying the refresh rate
>>> to 1 second. I have attached a screen recording to illustrate how it is
>>> functioning on my end.
>>> Do you have any thoughts on why the cursor issue might be happening?
>>>
>>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-22 11:15 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 0 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-08-22 11:15 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
On Tue, 22 Aug 2023 at 16:02, Khushboo Vashi <
[email protected]> wrote:
> Hi Sahil,
>
> On Tue, Aug 22, 2023 at 1:19 PM Sahil Harpal <[email protected]>
> wrote:
>
>> Hello Khushboo,
>>
>> Thank you for sharing this with me. As you can see, towards the end, when
>> you encountered the tooltip issue, the size of the graph container was
>> changing. I even found this bug in the actual pgAdmin4 application
>> installed on my system. Please see the attached video.
>>
>> Here's what I believe is happening: When the container's size changes, it
>> seems that tooltips continue to accumulate without being properly removed.
>> The method we use to display tooltips involves inserting a new element
>> into the DOM with the class name "uplot-tooltip". So, if we remove all the
>> existing tooltip elements before inserting a new one, it could potentially
>> resolve the issue. Would like to know your thoughts on this.
>>
>> It would be good if you fix this issue as you already investigated and
> found the root cause. But if you have other things to complete as the
> deadline is approaching you can create this issue at pgAdmin repo as it is
> an existing one.
>
Sure, I'll try to fix this.
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-22 18:04 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
1 sibling, 2 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-08-22 18:04 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers; Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
On Mon, 21 Aug 2023 at 09:59, Khushboo Vashi <
[email protected]> wrote:
> On Sat, Aug 19, 2023 at 4:21 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Pending Work:
>>>
>>> 1. Process information -
>>> - Issue: The pg_sys_process_info() query takes much longer (more
>>> than 2 mins) to execute and prevents the updation of other graphs and
>>> tables.
>>>
>>> This may happen on Windows, can you try to fetch these details
> separately (not combining with other charts) only for Windows?
>
I tried this, but it didn't work. I even created a separate endpoint to
fetch the data; however, it's still not working as expected. I believe it's
more of a server-side problem. I'm not entirely certain, but I think we're
executing the queries sequentially (Single threaded). This could be
preventing the updating of other graphs and tables.
Since the deadline is approaching, I'll first try to resolve other issues
and after that I'll look into this.
>
>>> 1. Disk information -
>>> - Issue: The pg_sys_disk_info() query returns NULL value for some
>>> of the drive letters.
>>>
>>> If it returns NULL then do not show anything for that field for
> particular Disk.
>
For disk statistics, we have decided to use pie and bar charts, and we need
drive letters for labeling. I just observed that the query is now returning
drive letters for both SSD and HDD partitions, which were failing earlier.
Maybe it was my system's fault. However, there are still some entries for
which the drive letter is missing.
[image: image.png]
But as @Dave Page <[email protected]> suggested:
On Mon, 24 Jul 2023 at 15:34, Dave Page <[email protected]> wrote:
> On your system, what are the volumes without letters etc? If they're
> things like swap/pagefile, recovery partition etc, then they can probably
> be omitted (e.g. SELECT ... WHERE mount_point IS NOT NULL OR drive_letter
> IS NOT NULL).
>
Can I omit the rows with null values?
Attachments:
[image/png] image.png (55.4K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-22 19:13 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-22 19:13 UTC (permalink / raw)
To: pgadmin-hackers; +Cc: Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>; Khushboo Vashi <[email protected]>
Also. what should I use for labeling: mount_point or drive_letter? It's
possible that the query might not return either of these values on some
systems.
For instance, in my case, it's returning values for drive_letter but null
for mount_point. However, in the example provided (here
<https://pgsnake.blogspot.com/2020/06/systemstats-extension-for-postgresql.html;),
it's returning paths for mount_point and null for drive_letter.
On Tue, 22 Aug 2023 at 23:34, Sahil Harpal <[email protected]>
wrote:
> On Mon, 21 Aug 2023 at 09:59, Khushboo Vashi <
> [email protected]> wrote:
>
>> On Sat, Aug 19, 2023 at 4:21 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Pending Work:
>>>>
>>>> 1. Process information -
>>>> - Issue: The pg_sys_process_info() query takes much longer (more
>>>> than 2 mins) to execute and prevents the updation of other graphs and
>>>> tables.
>>>>
>>>> This may happen on Windows, can you try to fetch these details
>> separately (not combining with other charts) only for Windows?
>>
>
> I tried this, but it didn't work. I even created a separate endpoint to
> fetch the data; however, it's still not working as expected. I believe it's
> more of a server-side problem. I'm not entirely certain, but I think we're
> executing the queries sequentially (Single threaded). This could be
> preventing the updating of other graphs and tables.
> Since the deadline is approaching, I'll first try to resolve other issues
> and after that I'll look into this.
>
>>
>>>> 1. Disk information -
>>>> - Issue: The pg_sys_disk_info() query returns NULL value for
>>>> some of the drive letters.
>>>>
>>>> If it returns NULL then do not show anything for that field for
>> particular Disk.
>>
> For disk statistics, we have decided to use pie and bar charts, and we
> need drive letters for labeling. I just observed that the query is now
> returning drive letters for both SSD and HDD partitions, which were failing
> earlier. Maybe it was my system's fault. However, there are still some
> entries for which the drive letter is missing.
> [image: image.png]
> But as @Dave Page <[email protected]> suggested:
> On Mon, 24 Jul 2023 at 15:34, Dave Page <[email protected]> wrote:
>
>> On your system, what are the volumes without letters etc? If they're
>> things like swap/pagefile, recovery partition etc, then they can probably
>> be omitted (e.g. SELECT ... WHERE mount_point IS NOT NULL OR drive_letter
>> IS NOT NULL).
>>
> Can I omit the rows with null values?
>
Attachments:
[image/png] image.png (55.4K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-23 06:07 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
1 sibling, 0 replies; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-23 06:07 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
On Tue, Aug 22, 2023 at 11:35 PM Sahil Harpal <[email protected]>
wrote:
> On Mon, 21 Aug 2023 at 09:59, Khushboo Vashi <
> [email protected]> wrote:
>
>> On Sat, Aug 19, 2023 at 4:21 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Pending Work:
>>>>
>>>> 1. Process information -
>>>> - Issue: The pg_sys_process_info() query takes much longer (more
>>>> than 2 mins) to execute and prevents the updation of other graphs and
>>>> tables.
>>>>
>>>> This may happen on Windows, can you try to fetch these details
>> separately (not combining with other charts) only for Windows?
>>
>
> I tried this, but it didn't work. I even created a separate endpoint to
> fetch the data; however, it's still not working as expected. I believe it's
> more of a server-side problem. I'm not entirely certain, but I think we're
> executing the queries sequentially (Single threaded). This could be
> preventing the updating of other graphs and tables.
> Since the deadline is approaching, I'll first try to resolve other issues
> and after that I'll look into this.
>
>>
>>>> 1. Disk information -
>>>> - Issue: The pg_sys_disk_info() query returns NULL value for
>>>> some of the drive letters.
>>>>
>>>> If it returns NULL then do not show anything for that field for
>> particular Disk.
>>
> For disk statistics, we have decided to use pie and bar charts, and we
> need drive letters for labeling. I just observed that the query is now
> returning drive letters for both SSD and HDD partitions, which were failing
> earlier. Maybe it was my system's fault. However, there are still some
> entries for which the drive letter is missing.
> [image: image.png]
>
But as @Dave Page <[email protected]> suggested:
> On Mon, 24 Jul 2023 at 15:34, Dave Page <[email protected]> wrote:
>
>> On your system, what are the volumes without letters etc? If they're
>> things like swap/pagefile, recovery partition etc, then they can probably
>> be omitted (e.g. SELECT ... WHERE mount_point IS NOT NULL OR drive_letter
>> IS NOT NULL).
>>
> Can I omit the rows with null values?
>
Yes.
Attachments:
[image/png] image.png (55.4K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-23 06:21 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-23 06:21 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
On Wed, Aug 23, 2023 at 12:44 AM Sahil Harpal <[email protected]>
wrote:
> Also. what should I use for labeling: mount_point or drive_letter? It's
> possible that the query might not return either of these values on some
> systems.
> For instance, in my case, it's returning values for drive_letter but null
> for mount_point. However, in the example provided (here
> <https://pgsnake.blogspot.com/2020/06/systemstats-extension-for-postgresql.html;),
> it's returning paths for mount_point and null for drive_letter.
>
Give first priority to Mount Point then drive letters (in most of the case
either of them will have value) and ignore if both are null.
>
> On Tue, 22 Aug 2023 at 23:34, Sahil Harpal <[email protected]>
> wrote:
>
>> On Mon, 21 Aug 2023 at 09:59, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>> On Sat, Aug 19, 2023 at 4:21 PM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> On Thu, 10 Aug 2023 at 00:37, Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Pending Work:
>>>>>
>>>>> 1. Process information -
>>>>> - Issue: The pg_sys_process_info() query takes much longer
>>>>> (more than 2 mins) to execute and prevents the updation of other graphs and
>>>>> tables.
>>>>>
>>>>> This may happen on Windows, can you try to fetch these details
>>> separately (not combining with other charts) only for Windows?
>>>
>>
>> I tried this, but it didn't work. I even created a separate endpoint to
>> fetch the data; however, it's still not working as expected. I believe it's
>> more of a server-side problem. I'm not entirely certain, but I think we're
>> executing the queries sequentially (Single threaded). This could be
>> preventing the updating of other graphs and tables.
>> Since the deadline is approaching, I'll first try to resolve other issues
>> and after that I'll look into this.
>>
>>>
>>>>> 1. Disk information -
>>>>> - Issue: The pg_sys_disk_info() query returns NULL value for
>>>>> some of the drive letters.
>>>>>
>>>>> If it returns NULL then do not show anything for that field for
>>> particular Disk.
>>>
>> For disk statistics, we have decided to use pie and bar charts, and we
>> need drive letters for labeling. I just observed that the query is now
>> returning drive letters for both SSD and HDD partitions, which were failing
>> earlier. Maybe it was my system's fault. However, there are still some
>> entries for which the drive letter is missing.
>> [image: image.png]
>> But as @Dave Page <[email protected]> suggested:
>> On Mon, 24 Jul 2023 at 15:34, Dave Page <[email protected]> wrote:
>>
>>> On your system, what are the volumes without letters etc? If they're
>>> things like swap/pagefile, recovery partition etc, then they can probably
>>> be omitted (e.g. SELECT ... WHERE mount_point IS NOT NULL OR drive_letter
>>> IS NOT NULL).
>>>
>> Can I omit the rows with null values?
>>
>
Attachments:
[image/png] image.png (55.4K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-24 14:37 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-24 14:37 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers; Dave Page <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
Hi,
Can we include total space stats (left pie chart) in the same bar chart? or
would it be better if we keep it separate?
[image: image.png]
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-24 14:57 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-24 14:57 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers
On Sat, 19 Aug 2023 at 16:20, Sahil Harpal <[email protected]>
wrote:
> On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
> [email protected]> wrote:
>
>>
>> - Sometimes graphs start from left and then shift to right. This
>> issue I have reported in my initial review too.
>>
>> For StreamingChart should I ignore it for now?
> On Mon, 7 Aug 2023 at 17:12, Aditya Toshniwal <
> [email protected]> wrote:
>
>> OK I thought you wanted the graph to go from left to right. Yeah, I
>> have seen that glitch a few times (but rare). I think you can ignore it for
>> now, we can look into it once we get time. It is not a priority.
>>
>
> Or try the solution below?
> On Sun, 6 Aug 2023 at 13:21, Sahil Harpal <[email protected]>
> wrote:
>
>> I've tried one more approach here. If our data array is full, this graph
>> shifting won't happen. So, if we initialize our data list with null values,
>> there won't be any visible shift. What do you think about this solution?
>>
> Can you please help me with this?
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 05:59 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-25 05:59 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
Hi Sahil,
On Thu, Aug 24, 2023 at 8:07 PM Sahil Harpal <[email protected]>
wrote:
> Hi,
> Can we include total space stats (left pie chart) in the same bar chart?
> or would it be better if we keep it separate?
> [image: image.png]
>
How do you propose to merge both? I would also suggest using a stacked bar
chart on the right. And format from bytes to MBs.
>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 06:00 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-25 06:00 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers
Hi Sahil,
On Thu, Aug 24, 2023 at 8:28 PM Sahil Harpal <[email protected]>
wrote:
> On Sat, 19 Aug 2023 at 16:20, Sahil Harpal <[email protected]>
> wrote:
>
>> On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>>
>>> - Sometimes graphs start from left and then shift to right. This
>>> issue I have reported in my initial review too.
>>>
>>> For StreamingChart should I ignore it for now?
>> On Mon, 7 Aug 2023 at 17:12, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> OK I thought you wanted the graph to go from left to right. Yeah, I
>>> have seen that glitch a few times (but rare). I think you can ignore it for
>>> now, we can look into it once we get time. It is not a priority.
>>>
>>
>> Or try the solution below?
>> On Sun, 6 Aug 2023 at 13:21, Sahil Harpal <[email protected]>
>> wrote:
>>
>>> I've tried one more approach here. If our data array is full, this graph
>>> shifting won't happen. So, if we initialize our data list with null values,
>>> there won't be any visible shift. What do you think about this solution?
>>>
>> Can you please help me with this?
>
Let me apply your patch and try once.
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 06:25 Aditya Toshniwal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 0 replies; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-25 06:25 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers
Hi Sahil,
On Fri, Aug 25, 2023 at 11:30 AM Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> On Thu, Aug 24, 2023 at 8:28 PM Sahil Harpal <[email protected]>
> wrote:
>
>> On Sat, 19 Aug 2023 at 16:20, Sahil Harpal <[email protected]>
>> wrote:
>>
>>> On Thu, 17 Aug 2023 at 17:57, Khushboo Vashi <
>>> [email protected]> wrote:
>>>
>>>>
>>>> - Sometimes graphs start from left and then shift to right. This
>>>> issue I have reported in my initial review too.
>>>>
>>>> For StreamingChart should I ignore it for now?
>>> On Mon, 7 Aug 2023 at 17:12, Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> OK I thought you wanted the graph to go from left to right. Yeah, I
>>>> have seen that glitch a few times (but rare). I think you can ignore it for
>>>> now, we can look into it once we get time. It is not a priority.
>>>>
>>>
>>> Or try the solution below?
>>> On Sun, 6 Aug 2023 at 13:21, Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> I've tried one more approach here. If our data array is full, this
>>>> graph shifting won't happen. So, if we initialize our data list with null
>>>> values, there won't be any visible shift. What do you think about this
>>>> solution?
>>>>
>>> Can you please help me with this?
>>
> Let me apply your patch and try once.
>
Can you please share the rebased patch?
>
>
> --
> Thanks,
> Aditya Toshniwal
> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
> <https://www.enterprisedb.com/;
> "Don't Complain about Heat, Plant a TREE"
>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 06:32 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-25 06:32 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
Hi Aditya,
On Fri, 25 Aug 2023 at 11:29, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
> On Thu, Aug 24, 2023 at 8:07 PM Sahil Harpal <[email protected]>
> wrote:
>
>> Hi,
>> Can we include total space stats (left pie chart) in the same bar chart?
>> or would it be better if we keep it separate?
>> [image: image.png]
>>
> How do you propose to merge both? I would also suggest using a stacked bar
> chart on the right. And format from bytes to MBs.
>
>>
Currently, I have implemented like this:
[image: image.png]
So, I was thinking of adding one more column for total space for each disk.
Regarding the stacked bar chart, we will need to increase the height of the
default chart container; otherwise, the proportion of different categories
won't be clearly visible in some cases.
And I believe that if we use a stacked bar chart, there won't be a need to
provide total space details, as the height of that stacked bar will be
nothing but the total space, right?
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
[image/png] image.png (13.6K, 4-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 06:36 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-25 06:36 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
Hi Sahil,
On Fri, Aug 25, 2023 at 12:02 PM Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
> On Fri, 25 Aug 2023 at 11:29, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>> On Thu, Aug 24, 2023 at 8:07 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hi,
>>> Can we include total space stats (left pie chart) in the same bar chart?
>>> or would it be better if we keep it separate?
>>> [image: image.png]
>>>
>> How do you propose to merge both? I would also suggest using a stacked
>> bar chart on the right. And format from bytes to MBs.
>>
>>>
> Currently, I have implemented like this:
> [image: image.png]
> So, I was thinking of adding one more column for total space for each disk.
> Regarding the stacked bar chart, we will need to increase the height of
> the default chart container; otherwise, the proportion of different
> categories won't be clearly visible in some cases.
> And I believe that if we use a stacked bar chart, there won't be a need to
> provide total space details, as the height of that stacked bar will be
> nothing but the total space, right?
>
I don't think we'll need to increase the height though. Even if you use
stacks, they will show total for each drive and not total available space.
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
[image/png] image.png (13.6K, 4-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 06:45 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-25 06:45 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
Hi Aditya,
On Fri, 25 Aug 2023 at 12:06, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> On Fri, Aug 25, 2023 at 12:02 PM Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Aditya,
>> On Fri, 25 Aug 2023 at 11:29, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> Hi Sahil,
>>> On Thu, Aug 24, 2023 at 8:07 PM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Hi,
>>>> Can we include total space stats (left pie chart) in the same bar
>>>> chart? or would it be better if we keep it separate?
>>>> [image: image.png]
>>>>
>>> How do you propose to merge both? I would also suggest using a stacked
>>> bar chart on the right. And format from bytes to MBs.
>>>
>>>>
>> Currently, I have implemented like this:
>> [image: image.png]
>> So, I was thinking of adding one more column for total space for each
>> disk.
>> Regarding the stacked bar chart, we will need to increase the height of
>> the default chart container; otherwise, the proportion of different
>> categories won't be clearly visible in some cases.
>> And I believe that if we use a stacked bar chart, there won't be a need
>> to provide total space details, as the height of that stacked bar will be
>> nothing but the total space, right?
>>
> I don't think we'll need to increase the height though. Even if you use
> stacks, they will show total for each drive and not total available space.
>
Ahh yes, users won't be able to see the absolute total value. So should I
change it to a stacked bar with 3 categories then?
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
[image/png] image.png (13.6K, 4-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 07:02 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-25 07:02 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
On Fri, Aug 25, 2023 at 12:15 PM Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
>
> On Fri, 25 Aug 2023 at 12:06, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>>
>> On Fri, Aug 25, 2023 at 12:02 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hi Aditya,
>>> On Fri, 25 Aug 2023 at 11:29, Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil,
>>>> On Thu, Aug 24, 2023 at 8:07 PM Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Hi,
>>>>> Can we include total space stats (left pie chart) in the same bar
>>>>> chart? or would it be better if we keep it separate?
>>>>> [image: image.png]
>>>>>
>>>> How do you propose to merge both? I would also suggest using a stacked
>>>> bar chart on the right. And format from bytes to MBs.
>>>>
>>>>>
>>> Currently, I have implemented like this:
>>> [image: image.png]
>>> So, I was thinking of adding one more column for total space for each
>>> disk.
>>> Regarding the stacked bar chart, we will need to increase the height of
>>> the default chart container; otherwise, the proportion of different
>>> categories won't be clearly visible in some cases.
>>> And I believe that if we use a stacked bar chart, there won't be a need
>>> to provide total space details, as the height of that stacked bar will be
>>> nothing but the total space, right?
>>>
>> I don't think we'll need to increase the height though. Even if you use
>> stacks, they will show total for each drive and not total available space.
>>
>
> Ahh yes, users won't be able to see the absolute total value. So should I
> change it to a stacked bar with 3 categories then?
>
I would suggest keeping the pie chart and making the stacks of used and
unused. If possible please keep the pie chart colors different from bar
chart.
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
[image/png] image.png (13.6K, 4-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 08:04 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-25 08:04 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
Hi Aditya,
On Fri, 25 Aug 2023 at 12:32, Aditya Toshniwal <
[email protected]> wrote:
>
> On Fri, Aug 25, 2023 at 12:15 PM Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Aditya,
>>
>> On Fri, 25 Aug 2023 at 12:06, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> Hi Sahil,
>>>
>>> On Fri, Aug 25, 2023 at 12:02 PM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Hi Aditya,
>>>> On Fri, 25 Aug 2023 at 11:29, Aditya Toshniwal <
>>>> [email protected]> wrote:
>>>> [image: image.gif]
>>>>
>>>>> Hi Sahil,
>>>>> On Thu, Aug 24, 2023 at 8:07 PM Sahil Harpal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>> Can we include total space stats (left pie chart) in the same bar
>>>>>> chart? or would it be better if we keep it separate?
>>>>>> [image: image.png]
>>>>>>
>>>>> How do you propose to merge both? I would also suggest using a stacked
>>>>> bar chart on the right. And format from bytes to MBs.
>>>>>
>>>>>> [image: image.gif]
>>>>>>
>>>>>
>>>> Currently, I have implemented like this:
>>>> [image: image.png]
>>>> So, I was thinking of adding one more column for total space for each
>>>> disk.
>>>> Regarding the stacked bar chart, we will need to increase the height of
>>>> the default chart container; otherwise, the proportion of different
>>>> categories won't be clearly visible in some cases.
>>>> And I believe that if we use a stacked bar chart, there won't be a need
>>>> to provide total space details, as the height of that stacked bar will be
>>>> nothing but the total space, right?
>>>>
>>> I don't think we'll need to increase the height though. Even if you use
>>> stacks, they will show total for each drive and not total available space.
>>>
>>
>> Ahh yes, users won't be able to see the absolute total value. So should I
>> change it to a stacked bar with 3 categories then?
>>
> I would suggest keeping the pie chart and making the stacks of used and
> unused. If possible please keep the pie chart colors different from bar
> chart.
>
Ok, sure I'll do it.
On Fri, 25 Aug 2023 at 11:56, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
> Can you please share the rebased patch?
>
Please find the attached patch with recent changes. It doesn't include this
stacked bar, but it contains things suggested in the second review, and I
also tried to resolve the tooltip issue. Let me know if it's working
correctly.
I have also pushed these changes here:
https://github.com/Sahil1479/pgadmin4/tree/system_stats [Branch:
system_stats]
I will share the final patch with a detailed summary of all the things that
have been changed once I complete this stacked/pie chart and a few more
refinements.
Thank you,
Sahil
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
[image/png] image.png (13.6K, 4-image.png)
download | view image
[image/gif] image.gif (42B, 5-image.gif)
download | view image
[image/gif] image.gif (42B, 6-image.gif)
download | view image
[application/octet-stream] system_stats.patch (108.9K, 7-system_stats.patch)
download | inline diff:
From 929d8f1ed0bcce78db795fe7cab9484c4e610a13 Mon Sep 17 00:00:00 2001
From: Sahil Harpal <[email protected]>
Date: Wed, 16 Aug 2023 15:19:15 +0530
Subject: [PATCH 1/3] System stats changes except process and disk information
---
web/pgadmin/dashboard/__init__.py | 131 ++++++
web/pgadmin/dashboard/static/js/Dashboard.jsx | 234 +++++++---
.../dashboard/static/js/SystemStats/CPU.jsx | 377 ++++++++++++++++
.../static/js/SystemStats/Memory.jsx | 372 +++++++++++++++
.../static/js/SystemStats/Storage.jsx | 329 ++++++++++++++
.../static/js/SystemStats/Summary.jsx | 422 ++++++++++++++++++
.../sql/default/system_statistics.sql | 100 +++++
.../js/components/PgChart/DonutChart.jsx | 70 +++
.../js/components/PgChart/StreamingChart.jsx | 146 ++++--
9 files changed, 2090 insertions(+), 91 deletions(-)
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
create mode 100644 web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
create mode 100644 web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
create mode 100644 web/pgadmin/static/js/components/PgChart/DonutChart.jsx
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py
index 1dac54e74..c18f5d3de 100644
--- a/web/pgadmin/dashboard/__init__.py
+++ b/web/pgadmin/dashboard/__init__.py
@@ -112,6 +112,72 @@ class DashboardModule(PgAdminModule):
help_str=help_string
)
+ self.hpc_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'hpc_stats_refresh',
+ gettext("Handle & Process count statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.cu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'cu_stats_refresh',
+ gettext(
+ "Percentage of CPU time used by different process \
+ modes statistics refresh rate"
+ ), 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.la_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'la_stats_refresh',
+ gettext("Average load statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.pcu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'pcu_stats_refresh',
+ gettext("CPU usage per process statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.m_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'm_stats_refresh',
+ gettext("Memory usage statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.sm_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'sm_stats_refresh',
+ gettext("Swap memory usage statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.pmu_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'pmu_stats_refresh',
+ gettext("Memory usage per process statistics refresh rate"),
+ 'integer', 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.io_stats_refresh = self.dashboard_preference.register(
+ 'dashboards', 'io_stats_refresh',
+ gettext("I/O analysis statistics refresh rate"), 'integer',
+ 5, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
self.display_graphs = self.dashboard_preference.register(
'display', 'show_graphs',
gettext("Show graphs?"), 'boolean', True,
@@ -197,6 +263,12 @@ class DashboardModule(PgAdminModule):
'dashboard.get_prepared_by_database_id',
'dashboard.config',
'dashboard.get_config_by_server_id',
+ 'dashboard.check_system_statistics',
+ 'dashboard.check_system_statistics_sid',
+ 'dashboard.check_system_statistics_did',
+ 'dashboard.system_statistics',
+ 'dashboard.system_statistics_sid',
+ 'dashboard.system_statistics_did',
]
@@ -536,3 +608,62 @@ def terminate_session(sid=None, did=None, pid=None):
response=gettext("Success") if res else gettext("Failed"),
status=200
)
+
+
+# To check whether system stats extesion is present or not
[email protected]('check_extension/system_statistics',
+ endpoint='check_system_statistics', methods=['GET'])
[email protected]('check_extension/system_statistics/<int:sid>',
+ endpoint='check_system_statistics_sid', methods=['GET'])
[email protected]('check_extension/system_statistics/<int:sid>/<int:did>',
+ endpoint='check_system_statistics_did', methods=['GET'])
+@login_required
+@check_precondition
+def check_system_statistics(sid=None, did=None):
+ sql = "SELECT * FROM pg_extension WHERE extname = 'system_stats';"
+ status, res = g.conn.execute_scalar(sql)
+ if not status:
+ return internal_server_error(errormsg=res)
+ data = {}
+ if res is not None:
+ data['ss_present'] = True
+ else:
+ data['ss_present'] = False
+ return ajax_response(
+ response=data,
+ status=200
+ )
+
+
+# System Statistics Backend
[email protected]('/system_statistics',
+ endpoint='system_statistics', methods=['GET'])
[email protected]('/system_statistics/<int:sid>',
+ endpoint='system_statistics_sid', methods=['GET'])
[email protected]('/system_statistics/<int:sid>/<int:did>',
+ endpoint='system_statistics_did', methods=['GET'])
+@login_required
+@check_precondition
+def system_statistics(sid=None, did=None):
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_statistics.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = json.loads(
+ chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 7194fcc10..e6afeff07 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -29,6 +29,10 @@ import _ from 'lodash';
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
import TabPanel from '../../../static/js/components/TabPanel';
+import Summary from 'SystemStats/Summary';
+import CPU from 'SystemStats/CPU';
+import Memory from 'SystemStats/Memory';
+import Storage from 'SystemStats/Storage';
function parseData(data) {
let res = [];
@@ -148,12 +152,21 @@ export default function Dashboard({
}) {
const classes = useStyles();
let tabs = [gettext('Sessions'), gettext('Locks'), gettext('Prepared Transactions')];
+ let mainTabs = [gettext('General'), gettext('System Statistics')];
+ let systemStatsTabs = [gettext('Summary'), gettext('CPU'), gettext('Memory'), gettext('Storage')];
const [dashData, setdashData] = useState([]);
const [msg, setMsg] = useState('');
+ const [ssMsg, setSsMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
+ const [mainTabVal, setmainTabVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [activeOnly, setActiveOnly] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
+ const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
+
+ const systemStatsTabChanged = (e, tabVal) => {
+ setSystemStatsTabVal(tabVal);
+ };
if (!did) {
tabs.push(gettext('Configuration'));
@@ -163,6 +176,10 @@ export default function Dashboard({
setTabVal(tabVal);
};
+ const mainTabChanged = (e, tabVal) => {
+ setmainTabVal(tabVal);
+ };
+
const serverConfigColumns = [
{
accessor: 'name',
@@ -745,6 +762,7 @@ export default function Dashboard({
useEffect(() => {
let url,
+ ss_extension_check_url = url_for('dashboard.check_system_statistics'),
message = gettext(
'Please connect to the selected server to view the dashboard.'
);
@@ -770,6 +788,10 @@ export default function Dashboard({
if (did) url += sid + '/' + did;
else url += sid;
+ if (did && !props.dbConnected) return;
+ if (did) ss_extension_check_url += '/' + sid + '/' + did;
+ else ss_extension_check_url += '/' + sid;
+
const api = getApiInstance();
if (node) {
api({
@@ -787,6 +809,20 @@ export default function Dashboard({
// show failed message.
setMsg(gettext('Failed to retrieve data from the server.'));
});
+
+ api({
+ url: ss_extension_check_url,
+ type: 'GET',
+ })
+ .then((res) => {
+ const data = res.data;
+ if(data['ss_present'] == false){
+ setSsMsg(gettext('System stats extension is not installed. You can install the extension in a database using the "CREATE EXTENSION system_stats;" SQL command. Reload the pgAdmin once you installed.'));
+ }
+ })
+ .catch(() => {
+ setSsMsg(gettext('Failed to verify the presence of system stats extension.'));
+ });
} else {
setMsg(message);
}
@@ -867,68 +903,148 @@ export default function Dashboard({
{sid && props.serverConnected ? (
<Box className={classes.dashboardPanel}>
<Box className={classes.emptyPanel}>
- {!_.isUndefined(preferences) && preferences.show_graphs && (
- <Graphs
- key={sid + did}
- preferences={preferences}
- sid={sid}
- did={did}
- pageVisible={props.panelVisible}
- ></Graphs>
- )}
- {!_.isUndefined(preferences) && preferences.show_activity && (
- <Box className={classes.panelContent}>
- <Box
- className={classes.cardHeader}
- title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
- >
- {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ <Box className={classes.panelContent}>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={mainTabVal}
+ onChange={mainTabChanged}
+ >
+ {mainTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
</Box>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
- <Tabs
- value={tabVal}
- onChange={tabChanged}
- >
- {tabs.map((tabValue) => {
- return <Tab key={tabValue} label={tabValue} />;
- })}
- <RefreshButton/>
- </Tabs>
+ {/* General Statistics */}
+ <TabPanel value={mainTabVal} index={0} classNameRoot={classes.tabPanel}>
+ {!_.isUndefined(preferences) && preferences.show_graphs && (
+ <Graphs
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ ></Graphs>
+ )}
+ {!_.isUndefined(preferences) && preferences.show_activity && (
+ <Box className={classes.panelContent}>
+ <Box
+ className={classes.cardHeader}
+ title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
+ >
+ {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ </Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={tabVal}
+ onChange={tabChanged}
+ >
+ {tabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
+ </Box>
+ <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ CustomHeader={CustomActiveOnlyHeader}
+ columns={activityColumns}
+ data={filteredDashData}
+ schema={schemaDict}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databaseLocksColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databasePreparedColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={serverConfigColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ </Box>
+ </Box>
+ )}
+ </TabPanel>
+ {/* System Statistics */}
+ <TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <Box height="100%" display="flex" flexDirection="column">
+ {ssMsg === '' ?
+ <>
+ <Box>
+ <Tabs
+ value={systemStatsTabVal}
+ onChange={systemStatsTabChanged}
+ >
+ {systemStatsTabs.map((tabValue) => {
+ return <Tab key={tabValue} label={tabValue} />;
+ })}
+ </Tabs>
+ </Box>
+ <TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
+ <Summary
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
+ <CPU
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
+ <Memory
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ <TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
+ <Storage
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ serverConnected={props.serverConnected}
+ />
+ </TabPanel>
+ </> :
+ <div className={classes.emptyPanel}>
+ <EmptyPanelMessage text={ssMsg}/>
+ </div>
+ }
</Box>
- <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- CustomHeader={CustomActiveOnlyHeader}
- columns={activityColumns}
- data={filteredDashData}
- schema={schemaDict}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databaseLocksColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databasePreparedColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={serverConfigColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- </Box>
+ </TabPanel>
</Box>
- )}
+ </Box>
</Box>
</Box>
) : showDefaultContents() }
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
new file mode 100644
index 000000000..276034d9e
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
@@ -0,0 +1,377 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2023, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+// eslint-disable-next-line react/display-name
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function formatBytes(bytes) {
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ let unitIndex = 0;
+
+ while (bytes >= 1024 && unitIndex < units.length - 1) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ return `${bytes.toFixed(2)} ${units[unitIndex]}`;
+}
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'cu_stats': {'User Normal': [], 'User Niced': [], 'Kernel': [], 'Idle': []},
+ 'la_stats': {'1 min': [], '5 mins': [], '10 mins': [], '15 mins': []},
+ 'pcu_stats': {},
+};
+
+export default function CPU({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [cpuUsageInfo, cpuUsageInfoReduce] = useReducer(statsReducer, chartsDefault['cu_stats']);
+ const [loadAvgInfo, loadAvgInfoReduce] = useReducer(statsReducer, chartsDefault['la_stats']);
+ const [processCpuUsageStats, setProcessCpuUsageStats] = useState([]);
+
+ const [, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'CPU Usage',
+ accessor: 'cpu_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['cu_stats_refresh'] != preferences['cu_stats_refresh']) {
+ cpuUsageInfoReduce({reset: chartsDefault['cu_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['la_stats_refresh'] != preferences['la_stats_refresh']) {
+ loadAvgInfoReduce({reset: chartsDefault['la_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['pcu_stats_refresh'] != preferences['pcu_stats_refresh']) {
+ setProcessCpuUsageStats({reset: chartsDefault['pcu_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('cu_stats')){
+ let new_cu_stats = {
+ 'User Normal': data['cu_stats']['usermode_normal_process_percent']?data['cu_stats']['usermode_normal_process_percent']:0,
+ 'User Niced': data['cu_stats']['usermode_niced_process_percent']?data['cu_stats']['usermode_niced_process_percent']:0,
+ 'Kernel': data['cu_stats']['kernelmode_process_percent']?data['cu_stats']['kernelmode_process_percent']:0,
+ 'Idle': data['cu_stats']['idle_mode_percent']?data['cu_stats']['idle_mode_percent']:0,
+ };
+ cpuUsageInfoReduce({incoming: new_cu_stats});
+ }
+
+ if(data.hasOwnProperty('la_stats')){
+ let new_la_stats = {
+ '1 min': data['la_stats']['load_avg_one_minute']?data['la_stats']['load_avg_one_minute']:0,
+ '5 mins': data['la_stats']['load_avg_five_minutes']?data['la_stats']['load_avg_five_minutes']:0,
+ '10 mins': data['la_stats']['load_avg_ten_minutes']?data['la_stats']['load_avg_ten_minutes']:0,
+ '15 mins': data['la_stats']['load_avg_fifteen_minutes']?data['la_stats']['load_avg_fifteen_minutes']:0,
+ };
+ loadAvgInfoReduce({incoming: new_la_stats});
+ }
+
+ if(data.hasOwnProperty('pcu_stats')){
+ let pcu_info_list = [];
+ const pcu_info_obj = data['pcu_stats'];
+ for (const key in pcu_info_obj) {
+ pcu_info_list.push({ icon: '', pid: pcu_info_obj[key]['pid'], name: pcu_info_obj[key]['name'], cpu_usage: formatBytes(pcu_info_obj[key]['cpu_usage']) });
+ }
+
+ setProcessCpuUsageStats(pcu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ cpuUsageInfoReduce({reset:chartsDefault['cu_stats']});
+ loadAvgInfoReduce({reset:chartsDefault['la_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <CPUWrapper
+ cpuUsageInfo={transformData(cpuUsageInfo, preferences['cu_stats_refresh'])}
+ loadAvgInfo={transformData(loadAvgInfo, preferences['la_stats_refresh'])}
+ processCpuUsageStats={processCpuUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+CPU.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function CPUWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('CPU Usage ()')}</div>
+ <ChartContainer id='cu-graph' title={gettext('')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.cpuUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Load Average')}</div>
+ <ChartContainer id='la-graph' title={gettext('')} datasets={props.loadAvgInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.loadAvgInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processCpuUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+CPUWrapper.propTypes = {
+ cpuUsageInfo: propTypeStats.isRequired,
+ loadAvgInfo: propTypeStats.isRequired,
+ processCpuUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
new file mode 100644
index 000000000..74e8f424b
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
@@ -0,0 +1,372 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import PgTable from 'sources/components/PgTable';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function formatBytes(bytes) {
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ let unitIndex = 0;
+
+ while (bytes >= 1024 && unitIndex < units.length - 1) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ return `${bytes.toFixed(2)} ${units[unitIndex]}`;
+}
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'sm_stats': {'Total': [], 'Used': [], 'Free': []},
+ 'pmu_stats': {},
+};
+
+export default function Memory({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [memoryUsageInfo, memoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['m_stats']);
+ const [swapMemoryUsageInfo, swapMemoryUsageInfoReduce] = useReducer(statsReducer, chartsDefault['sm_stats']);
+ const [processMemoryUsageStats, setProcessMemoryUsageStats] = useState([]);
+
+ const [, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'PID',
+ accessor: 'pid',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Usage',
+ accessor: 'memory_usage',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Memory Bytes',
+ accessor: 'memory_bytes',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['m_stats_refresh'] != preferences['m_stats_refresh']) {
+ memoryUsageInfoReduce({reset: chartsDefault['m_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['sm_stats_refresh'] != preferences['sm_stats_refresh']) {
+ swapMemoryUsageInfoReduce({reset: chartsDefault['sm_stats']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['pmu_stats_refresh'] != preferences['pmu_stats_refresh']) {
+ setProcessMemoryUsageStats({reset: chartsDefault['pmu_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('m_stats')){
+ let new_m_stats = {
+ 'Total': data['m_stats']['total_memory']?data['m_stats']['total_memory']:0,
+ 'Used': data['m_stats']['used_memory']?data['m_stats']['used_memory']:0,
+ 'Free': data['m_stats']['free_memory']?data['m_stats']['free_memory']:0,
+ };
+ memoryUsageInfoReduce({incoming: new_m_stats});
+ }
+
+ if(data.hasOwnProperty('sm_stats')){
+ let new_sm_stats = {
+ 'Total': data['sm_stats']['swap_total']?data['sm_stats']['swap_total']:0,
+ 'Used': data['sm_stats']['swap_used']?data['sm_stats']['swap_used']:0,
+ 'Free': data['sm_stats']['swap_free']?data['sm_stats']['swap_free']:0,
+ };
+ swapMemoryUsageInfoReduce({incoming: new_sm_stats});
+ }
+
+ if(data.hasOwnProperty('pmu_stats')){
+ let pmu_info_list = [];
+ const pmu_info_obj = data['pmu_stats'];
+ for (const key in pmu_info_obj) {
+ pmu_info_list.push({ icon: '', pid: pmu_info_obj[key]['pid'], name: pmu_info_obj[key]['name'], memory_usage: formatBytes(pmu_info_obj[key]['memory_usage']), memory_bytes: formatBytes(pmu_info_obj[key]['memory_bytes']) });
+ }
+
+ setProcessMemoryUsageStats(pmu_info_list);
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ memoryUsageInfoReduce({reset:chartsDefault['m_stats']});
+ swapMemoryUsageInfoReduce({reset:chartsDefault['sm_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <MemoryWrapper
+ memoryUsageInfo={transformData(memoryUsageInfo, preferences['m_stats_refresh'])}
+ swapMemoryUsageInfo={transformData(swapMemoryUsageInfo, preferences['sm_stats_refresh'])}
+ processMemoryUsageStats={processMemoryUsageStats}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Memory.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function MemoryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Memory')}</div>
+ <ChartContainer id='m-graph' title={gettext('')} datasets={props.memoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.memoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Swap Memory')}</div>
+ <ChartContainer id='sm-graph' title={gettext('')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.fixedContainer}>
+ <PgTable
+ className={classes.autoResizer}
+ columns={props.tableHeader}
+ data={props.processMemoryUsageStats}
+ msg={props.errorMsg}
+ type={'panel'}
+ ></PgTable>
+ </Grid>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+MemoryWrapper.propTypes = {
+ memoryUsageInfo: propTypeStats.isRequired,
+ swapMemoryUsageInfo: propTypeStats.isRequired,
+ processMemoryUsageStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
new file mode 100644
index 000000000..2acb217bd
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
@@ -0,0 +1,329 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ ioDiskContainer: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ },
+ fixedContainer: {
+ height: '577px',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+ chartHeader: {
+ fontSize: '14px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ }
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function ioStatsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(disk_stats => {
+ newState[disk_stats] = {};
+ Object.keys(action.incoming[disk_stats]).forEach(label => {
+ if(state[disk_stats][label]) {
+ newState[disk_stats][label] = [
+ action.counter ? action.incoming[disk_stats][label] - action.counterData[disk_stats][label] : action.incoming[disk_stats][label],
+ ...state[disk_stats][label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[disk_stats][label] = [
+ action.counter ? action.incoming[disk_stats][label] - action.counterData[disk_stats][label] : action.incoming[disk_stats][label],
+ ];
+ }
+ });
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'io_stats': {},
+};
+
+export default function Storage({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [ioInfo, ioInfoReduce] = useReducer(ioStatsReducer, chartsDefault['io_stats']);
+
+ const [, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['io_stats_refresh'] != preferences['io_stats_refresh']) {
+ ioInfoReduce({reset: chartsDefault['io_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('io_stats')){
+ const io_info_obj = data['io_stats'];
+ for (const disk in io_info_obj) {
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_total_rw`)){
+ chartsDefault.io_stats[`${disk}_total_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_total_rw`)){
+ ioInfo[`${disk}_total_rw`] = {'Read': [], 'Write': []};
+ }
+
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_bytes_rw`)){
+ chartsDefault.io_stats[`${disk}_bytes_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_bytes_rw`)){
+ ioInfo[`${disk}_bytes_rw`] = {'Read': [], 'Write': []};
+ }
+
+ if(!chartsDefault.io_stats.hasOwnProperty(`${disk}_time_rw`)){
+ chartsDefault.io_stats[`${disk}_time_rw`] = {'Read': [], 'Write': []};
+ }
+ if(!ioInfo.hasOwnProperty(`${disk}_time_rw`)){
+ ioInfo[`${disk}_time_rw`] = {'Read': [], 'Write': []};
+ }
+ }
+
+ let new_io_stats = {};
+ for (const disk in io_info_obj) {
+ new_io_stats[`${disk}_total_rw`] = {'Read': io_info_obj[`${disk}`]['total_reads']?io_info_obj[`${disk}`]['total_reads']:0, 'Write': io_info_obj[`${disk}`]['total_writes']?io_info_obj[`${disk}`]['total_writes']:0};
+ new_io_stats[`${disk}_bytes_rw`] = {'Read': io_info_obj[`${disk}`]['read_bytes']?io_info_obj[`${disk}`]['read_bytes']:0, 'Write': io_info_obj[`${disk}`]['write_bytes']?io_info_obj[`${disk}`]['write_bytes']:0};
+ new_io_stats[`${disk}_time_rw`] = {'Read': io_info_obj[`${disk}`]['read_time_ms']?io_info_obj[`${disk}`]['read_time_ms']:0, 'Write': io_info_obj[`${disk}`]['write_time_ms']?io_info_obj[`${disk}`]['write_time_ms']:0};
+ }
+ ioInfoReduce({incoming: new_io_stats});
+ }
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ ioInfoReduce({reset:chartsDefault['io_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <StorageWrapper
+ ioInfo={ioInfo}
+ ioRefreshRate={preferences['io_stats_refresh']}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Storage.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function StorageWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+
+ const keys = Object.keys(props.ioInfo);
+ return (
+ <>
+ {keys.map((key, index) => (
+ index % 3 === 0 && (
+ <Grid key={`disk-${index}`} container spacing={1} className={classes.container}>
+ <Grid container spacing={1} className={classes.ioDiskContainer}>
+ <div className={classes.containerHeader}>{gettext(`Disk ${Math.floor(index / 3) + 1}`)}</div>
+ </Grid>
+ <Grid container spacing={1} className={classes.ioDiskContainer}>
+ {keys.slice(index, index + 3).map((innerKey, innerKeyIndex) => (
+ <Grid key={`${innerKey}`} item md={4} sm={6}>
+ <div className={classes.chartHeader}>{innerKeyIndex==0 ? gettext('I/O Operations Count'): innerKeyIndex==1? gettext('Data Transfer (Bytes)'):gettext('Time Spent in I/O Operations (Milliseconds)')}</div>
+ <ChartContainer id={`io-graph-${innerKey}`} title={gettext('')} datasets={transformData(props.ioInfo[innerKey], props.ioRefreshRate).datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={transformData(props.ioInfo[innerKey], props.ioRefreshRate)} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ ))}
+ </Grid>
+ </Grid>
+ )
+ ))}
+ </>
+ );
+}
+
+StorageWrapper.propTypes = {
+ ioInfo: PropTypes.objectOf(
+ PropTypes.shape({
+ Read: PropTypes.array,
+ Write: PropTypes.array,
+ })
+ ),
+ ioRefreshRate: PropTypes.number.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ isTest: PropTypes.bool,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
new file mode 100644
index 000000000..9b2ee9a30
--- /dev/null
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
@@ -0,0 +1,422 @@
+import React, { useState, useEffect, useRef, useReducer, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import url_for from 'sources/url_for';
+import getApiInstance from 'sources/api_instance';
+import {getGCD, getEpoch} from 'sources/utils';
+import {ChartContainer} from '../Dashboard';
+import { Grid } from '@material-ui/core';
+import { DATA_POINT_SIZE } from 'sources/chartjs';
+import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
+import DonutChart from '../../../../static/js/components/PgChart/DonutChart';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import axios from 'axios';
+
+export const X_AXIS_LENGTH = 75;
+
+const useStyles = makeStyles((theme) => ({
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflowX: 'auto !important',
+ overflowY: 'hidden !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+ table: {
+ width: '100%',
+ backgroundColor: theme.otherVars.tableBg,
+ border: '1px solid rgb(221, 224, 230)',
+ },
+ tableVal: {
+ border: '1px solid rgb(221, 224, 230) !important',
+ padding: '10px !important',
+ },
+ container: {
+ height: 'auto',
+ background: theme.palette.grey[200],
+ padding: '10px',
+ marginBottom: '30px',
+ },
+ containerHeader: {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '5px',
+ },
+}));
+
+export function transformData(labels, refreshRate) {
+ const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ };
+ }) || [];
+
+ return {
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_statistics');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'hpc_stats': {'Handle': new Array(X_AXIS_LENGTH).fill(null), 'Process': new Array(X_AXIS_LENGTH).fill(null)},
+};
+
+const SummaryTable = (props) => {
+ const classes = useStyles();
+ const data = props.data;
+ return (
+ <table className={classes.table}>
+ <thead>
+ <tr>
+ <th className={classes.tableVal}>Property</th>
+ <th className={classes.tableVal}>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {data.map((item, index) => (
+ <tr className={classes.tableVal} key={index}>
+ <td className={classes.tableVal}>{item.name}</td>
+ <td className={classes.tableVal}>{item.value}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+};
+
+SummaryTable.propTypes = {
+ data: PropTypes.any,
+};
+
+export default function Summary({preferences, sid, did, pageVisible, enablePoll=true}) {
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [processHandleCount, processHandleCountReduce] = useReducer(statsReducer, chartsDefault['hpc_stats']);
+ const [osStats, setOsStats] = useState([]);
+ const [cpuStats, setCpuStats] = useState([]);
+ const [processInfoStats] = useState({'Running': 4, 'Sleeping': 2, 'Stopped': 1, 'Zombie': 2});
+
+ const [, setCounterData] = useState({});
+
+ const [pollDelay, setPollDelay] = useState(5000);
+ const [longPollDelay] = useState(180000);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ const tableHeader = [
+ {
+ Header: 'Property',
+ accessor: 'name',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ {
+ Header: 'Value',
+ accessor: 'value',
+ sortable: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ },
+ ];
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['hpc_stats_refresh'] != preferences['hpc_stats_refresh']) {
+ processHandleCountReduce({reset: chartsDefault['hpc_stats']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ const keys = Object.keys(chartsDefault);
+ const length = keys.length;
+ if(length == 1){
+ setPollDelay(
+ preferences[keys[0]+'_refresh']*1000
+ );
+ } else {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useEffect(() => {
+ try {
+ // Fetch the latest data point from the API endpoint
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'pg_sys_os_info,pg_sys_cpu_info';
+ const api = getApiInstance();
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ let data = res.data;
+
+ const os_info_obj = data['pg_sys_os_info'];
+ let os_info_list = [
+ { icon: '', name: 'Name', value: os_info_obj['name'] },
+ { icon: '', name: 'Version', value: os_info_obj['version'] },
+ { icon: '', name: 'Host name', value: os_info_obj['host_name'] },
+ { icon: '', name: 'Domain name', value: os_info_obj['domain_name'] },
+ { icon: '', name: 'Architecture', value: os_info_obj['architecture'] },
+ { icon: '', name: 'Os up since seconds', value: os_info_obj['os_up_since_seconds'] },
+ ];
+ setOsStats(os_info_list);
+
+ const cpu_info_obj = data['pg_sys_cpu_info'];
+ let cpu_info_list = [
+ { icon: '', name: 'Vendor', value: cpu_info_obj['vendor'] },
+ { icon: '', name: 'Description', value: cpu_info_obj['description'] },
+ { icon: '', name: 'Model name', value: cpu_info_obj['model_name'] },
+ { icon: '', name: 'No of cores', value: cpu_info_obj['no_of_cores'] },
+ { icon: '', name: 'Architecture', value: cpu_info_obj['architecture'] },
+ { icon: '', name: 'Clock speed Hz', value: cpu_info_obj['clock_speed_hz'] },
+ { icon: '', name: 'L1 dcache size', value: cpu_info_obj['l1dcache_size'] },
+ { icon: '', name: 'L1 icache size', value: cpu_info_obj['l1icache_size'] },
+ { icon: '', name: 'L2 cache size', value: cpu_info_obj['l2cache_size'] },
+ { icon: '', name: 'L3 cache size', value: cpu_info_obj['l3cache_size'] },
+ ];
+ setCpuStats(cpu_info_list);
+
+ setErrorMsg(null);
+ })
+ .catch((error) => {
+ console.error('Error fetching data:', error);
+ });
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ }, [sid, did, enablePoll, pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ processHandleCountReduce({incoming: data['hpc_stats']});
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ processHandleCountReduce({reset:chartsDefault['hpc_stats']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ useInterval(()=>{
+ // let url;
+ // url = url_for('dashboard.system_statistics');
+ // url += '/' + sid;
+ // url += did > 0 ? '/' + did : '';
+ // url += '?chart_names=' + 'pi_stats';
+ // axios.get(url)
+ // .then((resp)=>{
+ // let data = resp.data;
+ // console.log("pi data: ", data);
+ // })
+ // .catch((error)=>{
+ // if(!errorMsg) {
+ // if(error.response) {
+ // if (error.response.status === 428) {
+ // setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ // } else {
+ // setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ // }
+ // } else if(error.request) {
+ // setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ // return;
+ // } else {
+ // console.error(error);
+ // }
+ // }
+ // });
+ }, enablePoll ? longPollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' style={{display: 'none'}}>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <SummaryWrapper
+ processHandleCount={transformData(processHandleCount, preferences['hpc_stats_refresh'])}
+ osStats={osStats}
+ cpuStats={cpuStats}
+ processInfoStats={transformData(processInfoStats, 5)}
+ tableHeader={tableHeader}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ isTest={false}
+ />
+ }
+ </>
+ );
+}
+
+Summary.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+};
+
+export function SummaryWrapper(props) {
+ const classes = useStyles();
+ const options = useMemo(()=>({
+ showDataPoints: props.showDataPoints,
+ showTooltip: props.showTooltip,
+ lineBorderWidth: props.lineBorderWidth,
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ return (
+ <>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('OS Information')}</div>
+ <SummaryTable data={props.osStats} />
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Handle & Process Count')}</div>
+ <ChartContainer id='hpc-graph' title={gettext('')} datasets={props.processHandleCount.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={props.processHandleCount} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} showSecondAxis={true} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('CPU Information')}</div>
+ <SummaryTable data={props.cpuStats} />
+ </Grid>
+ <Grid item md={6} sm={12}>
+ <div className={classes.containerHeader}>{gettext('Process Information')}</div>
+ <ChartContainer id='pi-graph' title={gettext('')} datasets={props.processInfoStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <DonutChart data={props.processInfoStats.datasets} />
+ </ChartContainer>
+ </Grid>
+ </Grid>
+ </>
+ );
+}
+
+SummaryWrapper.propTypes = {
+ processHandleCount: PropTypes.any.isRequired,
+ osStats: PropTypes.any.isRequired,
+ cpuStats: PropTypes.any.isRequired,
+ processInfoStats: PropTypes.any.isRequired,
+ tableHeader: PropTypes.any.isRequired,
+ errorMsg: PropTypes.any,
+ showTooltip: PropTypes.bool,
+ showDataPoints: PropTypes.bool,
+ lineBorderWidth: PropTypes.number,
+ isDatabase: PropTypes.bool,
+ isTest: PropTypes.bool,
+};
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
new file mode 100644
index 000000000..9024a2c5e
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
@@ -0,0 +1,100 @@
+{% set add_union = false %}
+{% if 'pg_sys_os_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_os_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_os_info()) t
+{% endif %}
+{% if add_union and 'pg_sys_cpu_info' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pg_sys_cpu_info' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pg_sys_cpu_info' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_info()) t
+{% endif %}
+{% if add_union and 'hpc_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'hpc_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'hpc_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT
+ (SELECT handle_count FROM pg_sys_os_info()) AS "{{ _('Handle') }}",
+ (SELECT process_count FROM pg_sys_os_info()) AS "{{ _('Process') }}"
+ ) t
+{% endif %}
+{% if add_union and 'cu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'cu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'cu_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data
+ FROM (SELECT * FROM pg_sys_cpu_usage_info()) t
+{% endif %}
+{% if add_union and 'la_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'la_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'la_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_load_avg_info()) t
+{% endif %}
+{% if add_union and 'pcu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pcu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pcu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, cpu_usage, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT total_memory, used_memory, free_memory FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'sm_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'sm_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'sm_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT swap_total, swap_used, swap_free FROM pg_sys_memory_info()) t
+{% endif %}
+{% if add_union and 'pmu_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pmu_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pmu_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('process'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT pid, name, memory_usage, memory_bytes, ROW_NUMBER() OVER (ORDER BY pid) AS row_number
+ FROM pg_sys_cpu_memory_by_process()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'io_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'io_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'io_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('disk'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT *, ROW_NUMBER() OVER (ORDER BY device_name) AS row_number
+ FROM pg_sys_io_analysis_info()
+ ) t
+ ) AS chart_data
+{% endif %}
+{% if add_union and 'pi_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'pi_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'pi_stats' AS chart_name, pg_catalog.row_to_json(t) AS chart_data FROM (SELECT * FROM pg_sys_process_info()) t
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/DonutChart.jsx b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
new file mode 100644
index 000000000..4da1d4435
--- /dev/null
+++ b/web/pgadmin/static/js/components/PgChart/DonutChart.jsx
@@ -0,0 +1,70 @@
+import React, { useEffect, useRef } from 'react';
+import Chart from 'chart.js/auto';
+import PropTypes from 'prop-types';
+
+export default function DonutChart({ data }) {
+ const chartRef = useRef(null);
+ const chartInstance = useRef(null);
+
+ useEffect(() => {
+ if (data && Object.keys(data).length > 0) {
+ if (chartInstance.current) {
+ // If chart instance exists, update the data
+ chartInstance.current.data.labels = data.map((item) => item.label);
+ chartInstance.current.data.datasets[0].data = data.map((item) => item.data);
+ chartInstance.current.update();
+ } else {
+ // If chart instance doesn't exist, create a new chart
+ const chartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: false, // Hide the labels at the top
+ },
+ },
+ animation: {
+ duration: 0, // Disable the animation
+ },
+ tooltips: {
+ callbacks: {
+ label: function (tooltipItem, chartData) {
+ const dataset = chartData.datasets[tooltipItem.datasetIndex];
+ const total = dataset.data.reduce((previousValue, currentValue) => previousValue + currentValue);
+ const currentValue = dataset.data[tooltipItem.index];
+ const percentage = ((currentValue / total) * 100).toFixed(2) + '%';
+ return dataset.label + ': ' + currentValue + ' (' + percentage + ')';
+ },
+ },
+ },
+ };
+
+ const chartData = {
+ labels: data.map((item) => item.label),
+ datasets: [
+ {
+ data: data.map((item) => item.data),
+ backgroundColor: data.map((item) => item.borderColor),
+ hoverBackgroundColor: data.map((item) => item.borderColor),
+ },
+ ],
+ };
+
+ const ctx = chartRef.current.getContext('2d');
+ chartInstance.current = new Chart(ctx, {
+ type: 'doughnut',
+ data: chartData,
+ options: chartOptions,
+ });
+ }
+ }
+ }, [data]);
+
+ return (
+ <canvas ref={chartRef} />
+ );
+}
+
+DonutChart.propTypes = {
+ data: PropTypes.array.isRequired,
+};
\ No newline at end of file
diff --git a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
index bd465e3da..5ccfe3464 100644
--- a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
+++ b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
@@ -32,7 +32,7 @@ function tooltipPlugin(refreshRate) {
showTooltip();
let tooltipHtml=`<div>${(u.data[1].length-1-parseInt(u.legend.values[0]['_'])) * refreshRate + gettext(' seconds ago')}</div>`;
for(let i=1; i<u.series.length; i++) {
- tooltipHtml += `<div class="uplot-tooltip-label"><div style="height:12px; width:12px; background-color:${u.series[i].stroke()}"></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
+ tooltipHtml += `<div class='uplot-tooltip-label'><div style='height:12px; width:12px; background-color:${u.series[i].stroke()}'></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
}
tooltip.innerHTML = tooltipHtml;
@@ -58,44 +58,32 @@ function tooltipPlugin(refreshRate) {
};
}
-export default function StreamingChart({xRange=75, data, options}) {
+export default function StreamingChart({xRange=75, data, options, showSecondAxis=false}) {
const chartRef = useRef();
const theme = useTheme();
const { width, height, ref:containerRef } = useResizeDetector();
- const defaultOptions = useMemo(()=>({
- title: '',
- width: width,
- height: height,
- padding: [10, 0, 10, 0],
- focus: {
- alpha: 0.3,
- },
- cursor: {
- y: false,
- drag: {
- setScale: false,
- }
- },
- series: [
+ const defaultOptions = useMemo(()=> {
+ const series = [
{},
- ...(data.datasets?.map((datum)=>({
+ ...(data.datasets?.map((datum, index) => ({
label: datum.label,
stroke: datum.borderColor,
width: options.lineBorderWidth ?? 1,
- points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius*2 }
- }))??{})
- ],
- scales: {
- x: {
- time: false,
- }
- },
- axes: [
+ scale: showSecondAxis && (index === 1) ? 'y1' : 'y',
+ points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius * 2 },
+ })) ?? []),
+ ];
+
+ const axes = [
{
show: false,
stroke: theme.palette.text.primary,
},
- {
+ ];
+
+ if(showSecondAxis){
+ axes.push({
+ scale: 'y',
grid: {
stroke: theme.otherVars.borderColor,
width: 0.5,
@@ -108,11 +96,104 @@ export default function StreamingChart({xRange=75, data, options}) {
if(size < 40) size = 40;
}
return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ axes.push({
+ scale: 'y1',
+ side: 1,
+ stroke: theme.palette.text.primary,
+ grid: {show: false},
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
}
- }
- ],
- plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
- }), [data.refreshRate, data?.datasets?.length, width, height, options]);
+ });
+ } else{
+ axes.push({
+ scale: 'y',
+ grid: {
+ stroke: theme.otherVars.borderColor,
+ width: 0.5,
+ },
+ stroke: theme.palette.text.primary,
+ size: function(_obj, values) {
+ let size = 40;
+ if(values?.length > 0) {
+ size = values[values.length-1].length*12;
+ if(size < 40) size = 40;
+ }
+ return size;
+ },
+ // y-axis configuration
+ values: (self, ticks) => {
+ // Format the label
+ return ticks.map((value) => {
+ if(value < 1){
+ return value+'';
+ }
+ const suffixes = ['', 'k', 'M', 'B', 'T'];
+ const suffixNum = Math.floor(Math.log10(value) / 3);
+ const shortValue = (value / Math.pow(1000, suffixNum)).toFixed(1);
+ return shortValue + suffixes[suffixNum];
+ });
+ }
+ });
+ }
+
+ return {
+ title: '',
+ width: width,
+ height: height,
+ padding: [10, 0, 10, 0],
+ focus: {
+ alpha: 0.3,
+ },
+ cursor: {
+ y: false,
+ drag: {
+ setScale: false,
+ }
+ },
+ series: series,
+ scales: {
+ x: {
+ time: false,
+ }
+ },
+ axes: axes,
+ plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
+ };
+ }, [data.refreshRate, data?.datasets?.length, width, height, options]);
const initialState = [
Array.from(new Array(xRange).keys()),
@@ -140,4 +221,5 @@ StreamingChart.propTypes = {
xRange: PropTypes.number.isRequired,
data: propTypeData.isRequired,
options: PropTypes.object,
+ showSecondAxis: PropTypes.bool,
};
--
2.41.0.windows.1
From 7893b50d1b642ec9c8b66bb68fb8d19145a6b47d Mon Sep 17 00:00:00 2001
From: Sahil Harpal <[email protected]>
Date: Thu, 17 Aug 2023 15:31:59 +0530
Subject: [PATCH 2/3] Handle null values for CPU & memory usage
---
web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx | 4 ++++
web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
index 276034d9e..a6341954f 100644
--- a/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
+++ b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
@@ -54,6 +54,10 @@ const useStyles = makeStyles((theme) => ({
}));
export function formatBytes(bytes) {
+ if (bytes === null) {
+ return 'null';
+ }
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let unitIndex = 0;
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
index 74e8f424b..9d32795dd 100644
--- a/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
@@ -45,6 +45,10 @@ const useStyles = makeStyles((theme) => ({
}));
export function formatBytes(bytes) {
+ if (bytes === null) {
+ return 'null';
+ }
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let unitIndex = 0;
--
2.41.0.windows.1
From 8d8717f8fbb9345ac51c3592cdb6c1f4f867aa7d Mon Sep 17 00:00:00 2001
From: Sahil Harpal <[email protected]>
Date: Fri, 25 Aug 2023 13:10:10 +0530
Subject: [PATCH 3/3] Review-2 changes
---
web/pgadmin/dashboard/static/js/Dashboard.jsx | 3 +
.../dashboard/static/js/SystemStats/CPU.jsx | 6 +-
.../static/js/SystemStats/Memory.jsx | 4 +-
.../static/js/SystemStats/Storage.jsx | 267 ++++++++++++++++--
.../static/js/SystemStats/Summary.jsx | 12 +-
.../sql/default/system_statistics.sql | 13 +
.../js/components/PgChart/StreamingChart.jsx | 15 +-
7 files changed, 283 insertions(+), 37 deletions(-)
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index e6afeff07..2944a4e83 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -818,6 +818,8 @@ export default function Dashboard({
const data = res.data;
if(data['ss_present'] == false){
setSsMsg(gettext('System stats extension is not installed. You can install the extension in a database using the "CREATE EXTENSION system_stats;" SQL command. Reload the pgAdmin once you installed.'));
+ } else {
+ setSsMsg(gettext(''));
}
})
.catch(() => {
@@ -1034,6 +1036,7 @@ export default function Dashboard({
did={did}
pageVisible={props.panelVisible}
serverConnected={props.serverConnected}
+ systemStatsTabVal={systemStatsTabVal}
/>
</TabPanel>
</> :
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
index a6341954f..e6b766767 100644
--- a/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
+++ b/web/pgadmin/dashboard/static/js/SystemStats/CPU.jsx
@@ -36,13 +36,11 @@ const useStyles = makeStyles((theme) => ({
},
container: {
height: 'auto',
- background: theme.palette.grey[200],
padding: '10px',
marginBottom: '30px',
},
fixedContainer: {
height: '577px',
- background: theme.palette.grey[200],
padding: '10px',
marginBottom: '30px',
},
@@ -338,13 +336,13 @@ export function CPUWrapper(props) {
<>
<Grid container spacing={1} className={classes.container}>
<Grid item md={6} sm={12}>
- <div className={classes.containerHeader}>{gettext('CPU Usage ()')}</div>
+ <div className={classes.containerHeader}>{gettext('CPU usage')}</div>
<ChartContainer id='cu-graph' title={gettext('')} datasets={props.cpuUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.cpuUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
</ChartContainer>
</Grid>
<Grid item md={6} sm={12}>
- <div className={classes.containerHeader}>{gettext('Load Average')}</div>
+ <div className={classes.containerHeader}>{gettext('Load average')}</div>
<ChartContainer id='la-graph' title={gettext('')} datasets={props.loadAvgInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.loadAvgInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
</ChartContainer>
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
index 9d32795dd..8e8e0eb89 100644
--- a/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Memory.jsx
@@ -27,13 +27,11 @@ const useStyles = makeStyles((theme) => ({
},
container: {
height: 'auto',
- background: theme.palette.grey[200],
padding: '10px',
marginBottom: '30px',
},
fixedContainer: {
height: '577px',
- background: theme.palette.grey[200],
padding: '10px',
marginBottom: '30px',
},
@@ -339,7 +337,7 @@ export function MemoryWrapper(props) {
</ChartContainer>
</Grid>
<Grid item md={6} sm={12}>
- <div className={classes.containerHeader}>{gettext('Swap Memory')}</div>
+ <div className={classes.containerHeader}>{gettext('Swap memory')}</div>
<ChartContainer id='sm-graph' title={gettext('')} datasets={props.swapMemoryUsageInfo.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.swapMemoryUsageInfo} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
</ChartContainer>
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
index 2acb217bd..9db08b093 100644
--- a/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Storage.jsx
@@ -10,6 +10,7 @@ import { DATA_POINT_SIZE } from 'sources/chartjs';
import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
import {useInterval, usePrevious} from 'sources/custom_hooks';
import axios from 'axios';
+import { BarChart } from '../../../../static/js/chartjs';
export const X_AXIS_LENGTH = 75;
@@ -26,22 +27,24 @@ const useStyles = makeStyles((theme) => ({
},
container: {
height: 'auto',
- background: theme.palette.grey[200],
padding: '10px',
- marginBottom: '30px',
+ marginBottom: '15px',
},
ioDiskContainer: {
height: 'auto',
- background: theme.palette.grey[200],
padding: '10px',
},
fixedContainer: {
height: '577px',
- background: theme.palette.grey[200],
padding: '10px',
marginBottom: '30px',
+ overflowX: 'auto',
},
containerHeader: {
+ height: 'auto',
+ padding: '10px',
+ },
+ containerHeaderText: {
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '5px',
@@ -50,9 +53,37 @@ const useStyles = makeStyles((theme) => ({
fontSize: '14px',
fontWeight: 'bold',
marginBottom: '5px',
- }
+ },
+ table: {
+ width: '100%',
+ backgroundColor: theme.otherVars.tableBg,
+ border: '1px solid rgb(221, 224, 230)',
+ borderCollapse: 'collapse',
+ borderRadius: '4px',
+ overflow: 'hidden',
+ },
+ tableVal: {
+ border: '1px solid rgb(221, 224, 230) !important',
+ padding: '10px !important',
+ },
}));
+export function formatBytes(bytes) {
+ if (bytes === null) {
+ return 'null';
+ }
+
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ let unitIndex = 0;
+
+ while (bytes >= 1024 && unitIndex < units.length - 1) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ return `${bytes.toFixed(2)} ${units[unitIndex]}`;
+}
+
export function transformData(labels, refreshRate) {
const colors = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40','#8D6E63','#2196F3','#FFEB3B','#9C27B0','#00BCD4','#CDDC39'];
let datasets = Object.keys(labels).map((label, i)=>{
@@ -120,10 +151,43 @@ const chartsDefault = {
'io_stats': {},
};
-export default function Storage({preferences, sid, did, pageVisible, enablePoll=true}) {
+
+const DiskStatsTable = (props) => {
+ const classes = useStyles();
+ const tableHeader = props.tableHeader;
+ const data = props.data;
+ return (
+ <table className={classes.table}>
+ <thead>
+ <tr>
+ {tableHeader.map((item, index) => (
+ <th className={classes.tableVal} key={index}>{item.Header}</th>
+ ))}
+ </tr>
+ </thead>
+ <tbody>
+ {data.map((item, index) => (
+ <tr className={classes.tableVal} key={index}>
+ {tableHeader.map((header, id) => (
+ <td className={classes.tableVal} key={header.accessor+'-'+id}>{item[header.accessor]}</td>
+ ))}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+};
+
+DiskStatsTable.propTypes = {
+ data: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
+};
+
+export default function Storage({preferences, sid, did, pageVisible, enablePoll=true, systemStatsTabVal}) {
const refreshOn = useRef(null);
const prevPrefernces = usePrevious(preferences);
+ const [diskStats, setDiskStats] = useState([]);
const [ioInfo, ioInfoReduce] = useReducer(ioStatsReducer, chartsDefault['io_stats']);
const [, setCounterData] = useState({});
@@ -132,6 +196,49 @@ export default function Storage({preferences, sid, did, pageVisible, enablePoll=
const [errorMsg, setErrorMsg] = useState(null);
const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+ const tableHeader = [
+ {
+ Header: 'File system',
+ accessor: 'file_system',
+ },
+ {
+ Header: 'File system type',
+ accessor: 'file_system_type',
+ },
+ {
+ Header: 'Mount point',
+ accessor: 'mount_point',
+ },
+ {
+ Header: 'Drive letter',
+ accessor: 'drive_letter',
+ },
+ {
+ Header: 'Total space',
+ accessor: 'total_space',
+ },
+ {
+ Header: 'Used space',
+ accessor: 'used_space',
+ },
+ {
+ Header: 'Free space',
+ accessor: 'free_space',
+ },
+ {
+ Header: 'Total inodes',
+ accessor: 'total_inodes',
+ },
+ {
+ Header: 'Used inodes',
+ accessor: 'used_inodes',
+ },
+ {
+ Header: 'Free inodes',
+ accessor: 'free_inodes',
+ },
+ ];
+
useEffect(()=>{
let calcPollDelay = false;
if(prevPrefernces) {
@@ -164,6 +271,50 @@ export default function Storage({preferences, sid, did, pageVisible, enablePoll=
}
}, [pageVisible]);
+ useEffect(() => {
+ try {
+ // Fetch the latest data point from the API endpoint
+ let url;
+ url = url_for('dashboard.system_statistics');
+ url += '/' + sid;
+ url += did > 0 ? '/' + did : '';
+ url += '?chart_names=' + 'di_stats';
+ axios.get(url)
+ .then((res) => {
+ let data = res.data;
+ setErrorMsg(null);
+ if(data.hasOwnProperty('di_stats')){
+ let di_info_list = [];
+ const di_info_obj = data['di_stats'];
+ for (const key in di_info_obj) {
+ di_info_list.push({
+ icon: '',
+ file_system: di_info_obj[key]['file_system']?di_info_obj[key]['file_system']:'null',
+ file_system_type: di_info_obj[key]['file_system_type']?di_info_obj[key]['file_system_type']:'null',
+ mount_point: di_info_obj[key]['mount_point']?di_info_obj[key]['mount_point']:'null',
+ drive_letter: di_info_obj[key]['drive_letter']?di_info_obj[key]['drive_letter']:'null',
+ total_space: di_info_obj[key]['total_space']?formatBytes(di_info_obj[key]['total_space']):'null',
+ used_space: di_info_obj[key]['used_space']?formatBytes(di_info_obj[key]['used_space']):'null',
+ free_space: di_info_obj[key]['free_space']?formatBytes(di_info_obj[key]['free_space']):'null',
+ total_inodes: di_info_obj[key]['total_inodes']?di_info_obj[key]['total_inodes']:'null',
+ used_inodes: di_info_obj[key]['used_inodes']?di_info_obj[key]['used_inodes']:'null',
+ free_inodes: di_info_obj[key]['free_inodes']?di_info_obj[key]['free_inodes']:'null',
+ total_space_actual: di_info_obj[key]['total_space']?di_info_obj[key]['total_space']:null,
+ used_space_actual: di_info_obj[key]['used_space']?di_info_obj[key]['used_space']:null,
+ free_space_actual: di_info_obj[key]['free_space']?di_info_obj[key]['free_space']:null,
+ });
+ }
+ setDiskStats(di_info_list);
+ }
+ })
+ .catch((error) => {
+ console.error('Error fetching data:', error);
+ });
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ }, [systemStatsTabVal, sid, did, enablePoll, pageVisible]);
+
useInterval(()=>{
const currEpoch = getEpoch();
if(refreshOn.current === null) {
@@ -258,6 +409,8 @@ export default function Storage({preferences, sid, did, pageVisible, enablePoll=
<StorageWrapper
ioInfo={ioInfo}
ioRefreshRate={preferences['io_stats_refresh']}
+ diskStats={diskStats}
+ tableHeader={tableHeader}
errorMsg={errorMsg}
showTooltip={preferences['graph_mouse_track']}
showDataPoints={preferences['graph_data_points']}
@@ -276,6 +429,7 @@ Storage.propTypes = {
did: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]),
pageVisible: PropTypes.bool,
enablePoll: PropTypes.bool,
+ systemStatsTabVal: PropTypes.number,
};
export function StorageWrapper(props) {
@@ -289,25 +443,88 @@ export function StorageWrapper(props) {
const keys = Object.keys(props.ioInfo);
return (
<>
- {keys.map((key, index) => (
- index % 3 === 0 && (
- <Grid key={`disk-${index}`} container spacing={1} className={classes.container}>
- <Grid container spacing={1} className={classes.ioDiskContainer}>
- <div className={classes.containerHeader}>{gettext(`Disk ${Math.floor(index / 3) + 1}`)}</div>
- </Grid>
- <Grid container spacing={1} className={classes.ioDiskContainer}>
- {keys.slice(index, index + 3).map((innerKey, innerKeyIndex) => (
- <Grid key={`${innerKey}`} item md={4} sm={6}>
- <div className={classes.chartHeader}>{innerKeyIndex==0 ? gettext('I/O Operations Count'): innerKeyIndex==1? gettext('Data Transfer (Bytes)'):gettext('Time Spent in I/O Operations (Milliseconds)')}</div>
- <ChartContainer id={`io-graph-${innerKey}`} title={gettext('')} datasets={transformData(props.ioInfo[innerKey], props.ioRefreshRate).datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
- <StreamingChart data={transformData(props.ioInfo[innerKey], props.ioRefreshRate)} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
- </ChartContainer>
- </Grid>
- ))}
- </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid container spacing={1} className={classes.containerHeader}>
+ <div className={classes.containerHeaderText}>{gettext('Disk information')}</div>
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <DiskStatsTable tableHeader={props.tableHeader} data={props.diskStats} />
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ <Grid item md={6} sm={12}>
+ <ChartContainer id='ua-space-graph' title={gettext('')} datasets={[{borderColor: '#FF6384', label: 'Used space'}, {borderColor: '#36a2eb', label: 'Available space'}]} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <BarChart data={{
+ labels: props.diskStats.map((item, index) => item.mount_point!='null'?item.mount_point:item.drive_letter!='null'?item.drive_letter:'disk'+index),
+ datasets: [
+ {
+ label: 'Used space',
+ data: props.diskStats.map((item) => item.used_space_actual?item.used_space_actual:0),
+ backgroundColor: '#FF6384',
+ borderColor: '#FF6384',
+ borderWidth: 1,
+ },
+ {
+ label: 'Available space',
+ data: props.diskStats.map((item) => item.free_space_actual?item.free_space_actual:0),
+ backgroundColor: '#36a2eb',
+ borderColor: '#36a2eb',
+ borderWidth: 1,
+ },
+ ],
+ }}
+ options={
+ {
+ scales: {
+ x: {
+ display: true,
+ ticks: {
+ display: true,
+ },
+ },
+ y: {
+ beginAtZero: true,
+ ticks: {
+ callback: function (value) {
+ return formatBytes(value);
+ },
+ },
+ },
+ },
+ plugins: {
+ legend: {
+ display: false,
+ },
+ },
+ }
+ }
+ />
+ </ChartContainer>
</Grid>
- )
- ))}
+ <Grid item md={6} sm={12}>
+ </Grid>
+ </Grid>
+ </Grid>
+ <Grid container spacing={1} className={classes.container}>
+ {keys.map((key, index) => (
+ index % 3 === 0 && (
+ <Grid key={`disk-${index}`} container spacing={1} className={classes.container}>
+ <Grid container spacing={1} className={classes.containerHeader}>
+ <div className={classes.containerHeaderText}>{gettext(`Disk ${Math.floor(index / 3) + 1}`)}</div>
+ </Grid>
+ <Grid container spacing={1} className={classes.ioDiskContainer}>
+ {keys.slice(index, index + 3).map((innerKey, innerKeyIndex) => (
+ <Grid key={`${innerKey}`} item md={4} sm={6}>
+ <div className={classes.chartHeader}>{innerKeyIndex==0 ? gettext('I/O operations count'): innerKeyIndex==1? gettext('Data transfer (bytes)'):gettext('Time spent in I/O operations (milliseconds)')}</div>
+ <ChartContainer id={`io-graph-${innerKey}`} title={gettext('')} datasets={transformData(props.ioInfo[innerKey], props.ioRefreshRate).datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
+ <StreamingChart data={transformData(props.ioInfo[innerKey], props.ioRefreshRate)} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
+ </ChartContainer>
+ </Grid>
+ ))}
+ </Grid>
+ </Grid>
+ )
+ ))}
+ </Grid>
</>
);
}
@@ -320,6 +537,8 @@ StorageWrapper.propTypes = {
})
),
ioRefreshRate: PropTypes.number.isRequired,
+ diskStats: PropTypes.array.isRequired,
+ tableHeader: PropTypes.array.isRequired,
errorMsg: PropTypes.string,
showTooltip: PropTypes.bool.isRequired,
showDataPoints: PropTypes.bool.isRequired,
diff --git a/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
index 9b2ee9a30..1cda27a01 100644
--- a/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
+++ b/web/pgadmin/dashboard/static/js/SystemStats/Summary.jsx
@@ -30,6 +30,9 @@ const useStyles = makeStyles((theme) => ({
width: '100%',
backgroundColor: theme.otherVars.tableBg,
border: '1px solid rgb(221, 224, 230)',
+ borderCollapse: 'collapse',
+ borderRadius: '4px',
+ overflow: 'hidden',
},
tableVal: {
border: '1px solid rgb(221, 224, 230) !important',
@@ -37,7 +40,6 @@ const useStyles = makeStyles((theme) => ({
},
container: {
height: 'auto',
- background: theme.palette.grey[200],
padding: '10px',
marginBottom: '30px',
},
@@ -381,11 +383,11 @@ export function SummaryWrapper(props) {
<>
<Grid container spacing={1} className={classes.container}>
<Grid item md={6} sm={12}>
- <div className={classes.containerHeader}>{gettext('OS Information')}</div>
+ <div className={classes.containerHeader}>{gettext('OS information')}</div>
<SummaryTable data={props.osStats} />
</Grid>
<Grid item md={6} sm={12}>
- <div className={classes.containerHeader}>{gettext('Handle & Process Count')}</div>
+ <div className={classes.containerHeader}>{gettext('Handle & process count')}</div>
<ChartContainer id='hpc-graph' title={gettext('')} datasets={props.processHandleCount.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<StreamingChart data={props.processHandleCount} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} showSecondAxis={true} />
</ChartContainer>
@@ -393,11 +395,11 @@ export function SummaryWrapper(props) {
</Grid>
<Grid container spacing={1} className={classes.container}>
<Grid item md={6} sm={12}>
- <div className={classes.containerHeader}>{gettext('CPU Information')}</div>
+ <div className={classes.containerHeader}>{gettext('CPU information')}</div>
<SummaryTable data={props.cpuStats} />
</Grid>
<Grid item md={6} sm={12}>
- <div className={classes.containerHeader}>{gettext('Process Information')}</div>
+ <div className={classes.containerHeader}>{gettext('Process information')}</div>
<ChartContainer id='pi-graph' title={gettext('')} datasets={props.processInfoStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
<DonutChart data={props.processInfoStats.datasets} />
</ChartContainer>
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
index 9024a2c5e..35fe9042d 100644
--- a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_statistics.sql
@@ -91,6 +91,19 @@
) t
) AS chart_data
{% endif %}
+{% if add_union and 'di_stats' in chart_names %}
+ UNION ALL
+{% endif %}
+{% if 'di_stats' in chart_names %}
+{% set add_union = true %}
+ SELECT 'di_stats' AS chart_name, (
+ SELECT to_json(pg_catalog.jsonb_object_agg('Drive'||row_number, pg_catalog.row_to_json(t)))
+ FROM (
+ SELECT *, ROW_NUMBER() OVER (ORDER BY total_space) AS row_number
+ FROM pg_sys_disk_info() WHERE mount_point IS NOT NULL OR drive_letter IS NOT NULL
+ ) t
+ ) AS chart_data
+{% endif %}
{% if add_union and 'pi_stats' in chart_names %}
UNION ALL
{% endif %}
diff --git a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
index 5ccfe3464..34a1a8fea 100644
--- a/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
+++ b/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
@@ -5,6 +5,16 @@ import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { useTheme } from '@material-ui/styles';
+const removeExistingTooltips = () => {
+ // Select all elements with the class name "uplot-tooltip"
+ const tooltipLabels = document.querySelectorAll('.uplot-tooltip');
+
+ // Remove each selected element
+ tooltipLabels.forEach((tooltipLabel) => {
+ tooltipLabel.remove();
+ });
+};
+
function tooltipPlugin(refreshRate) {
let tooltipTopOffset = -20;
let tooltipLeftOffset = 10;
@@ -12,13 +22,14 @@ function tooltipPlugin(refreshRate) {
function showTooltip() {
if(!tooltip) {
+ removeExistingTooltips();
tooltip = document.createElement('div');
tooltip.className = 'uplot-tooltip';
tooltip.style.display = 'block';
document.body.appendChild(tooltip);
}
}
-
+
function hideTooltip() {
tooltip?.remove();
tooltip = null;
@@ -62,6 +73,7 @@ export default function StreamingChart({xRange=75, data, options, showSecondAxis
const chartRef = useRef();
const theme = useTheme();
const { width, height, ref:containerRef } = useResizeDetector();
+
const defaultOptions = useMemo(()=> {
const series = [
{},
@@ -170,6 +182,7 @@ export default function StreamingChart({xRange=75, data, options, showSecondAxis
});
}
+
return {
title: '',
width: width,
--
2.41.0.windows.1
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-25 08:11 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 0 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-08-25 08:11 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: Khushboo Vashi <[email protected]>; pgadmin-hackers; Dave Page <[email protected]>; Akshay Joshi <[email protected]>; Ashesh Vashi <[email protected]>
I have added the idea of array initialization for the 'Process & handle
count' chart. Let me know if similar can be used for all the streaming
charts to resolve the graph shifting issue.
On Fri, 25 Aug 2023 at 13:34, Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
> On Fri, 25 Aug 2023 at 12:32, Aditya Toshniwal <
> [email protected]> wrote:
>
>>
>> On Fri, Aug 25, 2023 at 12:15 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hi Aditya,
>>>
>>> On Fri, 25 Aug 2023 at 12:06, Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil,
>>>>
>>>> On Fri, Aug 25, 2023 at 12:02 PM Sahil Harpal <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Aditya,
>>>>> On Fri, 25 Aug 2023 at 11:29, Aditya Toshniwal <
>>>>> [email protected]> wrote:
>>>>> [image: image.gif]
>>>>>
>>>>>> Hi Sahil,
>>>>>> On Thu, Aug 24, 2023 at 8:07 PM Sahil Harpal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>> Can we include total space stats (left pie chart) in the same bar
>>>>>>> chart? or would it be better if we keep it separate?
>>>>>>> [image: image.png]
>>>>>>>
>>>>>> How do you propose to merge both? I would also suggest using a
>>>>>> stacked bar chart on the right. And format from bytes to MBs.
>>>>>>
>>>>>>> [image: image.gif]
>>>>>>>
>>>>>>
>>>>> Currently, I have implemented like this:
>>>>> [image: image.png]
>>>>> So, I was thinking of adding one more column for total space for each
>>>>> disk.
>>>>> Regarding the stacked bar chart, we will need to increase the height
>>>>> of the default chart container; otherwise, the proportion of different
>>>>> categories won't be clearly visible in some cases.
>>>>> And I believe that if we use a stacked bar chart, there won't be a
>>>>> need to provide total space details, as the height of that stacked bar will
>>>>> be nothing but the total space, right?
>>>>>
>>>> I don't think we'll need to increase the height though. Even if you use
>>>> stacks, they will show total for each drive and not total available space.
>>>>
>>>
>>> Ahh yes, users won't be able to see the absolute total value. So should
>>> I change it to a stacked bar with 3 categories then?
>>>
>> I would suggest keeping the pie chart and making the stacks of used and
>> unused. If possible please keep the pie chart colors different from bar
>> chart.
>>
> Ok, sure I'll do it.
>
> On Fri, 25 Aug 2023 at 11:56, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>> Can you please share the rebased patch?
>>
> Please find the attached patch with recent changes. It doesn't include
> this stacked bar, but it contains things suggested in the second review,
> and I also tried to resolve the tooltip issue. Let me know if it's working
> correctly.
> I have also pushed these changes here:
> https://github.com/Sahil1479/pgadmin4/tree/system_stats [Branch:
> system_stats]
>
> I will share the final patch with a detailed summary of all the things
> that have been changed once I complete this stacked/pie chart and a few
> more refinements.
>
> Thank you,
> Sahil
>
Attachments:
[image/png] image.png (44.9K, 3-image.png)
download | view image
[image/png] image.png (13.6K, 4-image.png)
download | view image
[image/gif] image.gif (42B, 5-image.gif)
download | view image
[image/gif] image.gif (42B, 6-image.gif)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-26 06:06 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-26 06:06 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
Hi Khushboo,
On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
[email protected]> wrote:
> Sahil, once the issues get resolved, please raise the PR and we will do
> the final review there.
>
Could you please tell me to which branch I should raise the PR?
Also, should I remove the code responsible for the static DonutChart of
process information?
Thank you,
Sahil
>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-27 01:43 Khushboo Vashi <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Khushboo Vashi @ 2023-08-27 01:43 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]> wrote:
> Hi Khushboo,
>
> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
> [email protected]> wrote:
>
>> Sahil, once the issues get resolved, please raise the PR and we will do
>> the final review there.
>>
> Could you please tell me to which branch I should raise the PR?
>
Master branch
> Also, should I remove the code responsible for the static DonutChart of
> process information?
>
> Thank you,
> Sahil
>
>>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-27 12:27 Sahil Harpal <[email protected]>
parent: Khushboo Vashi <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-27 12:27 UTC (permalink / raw)
To: pgadmin-hackers; +Cc: Khushboo Vashi <[email protected]>; Aditya Toshniwal <[email protected]>; Akshay Joshi <[email protected]>; Dave Page <[email protected]>; Ashesh Vashi <[email protected]>
Hello everyone,
I have raised the PR <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
I would like to request you all to review the changes and provide your
valuable feedback. Your insights and suggestions would be invaluable in
ensuring the quality and accuracy of the codebase.
Thank you,
Sahil
On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
[email protected]> wrote:
>
>
> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
> wrote:
>
>> Hi Khushboo,
>>
>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>> Sahil, once the issues get resolved, please raise the PR and we will do
>>> the final review there.
>>>
>> Could you please tell me to which branch I should raise the PR?
>>
> Master branch
>
>> Also, should I remove the code responsible for the static DonutChart of
>> process information?
>>
>> Thank you,
>> Sahil
>>
>>>
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-28 05:13 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-28 05:13 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Khushboo Vashi <[email protected]>; Akshay Joshi <[email protected]>; Dave Page <[email protected]>; Ashesh Vashi <[email protected]>
Hi Sahil,
I have few observations. You have added separate titles for graphs and
other tabular data. This is inconsistent with existing UI.
For example,
[image: image.png]
like here:
[image: image.png]
[image: Screenshot 2023-08-28 at 10.28.59 AM.png]
like here:
[image: image.png]
[image: image.png]
[image: image.png]
The dashboard goes blank when I change refresh rates.
[image: image.png]
And regarding filling with nulls to fix the reversing issue of graph - you
can do it in StreamingChart itself when setting initialState var, as it is
StreamingChart's responsibility to do it.
Next review will be on the PR directly.
On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <[email protected]>
wrote:
> Hello everyone,
>
> I have raised the PR <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
> I would like to request you all to review the changes and provide your
> valuable feedback. Your insights and suggestions would be invaluable in
> ensuring the quality and accuracy of the codebase.
>
> Thank you,
> Sahil
>
> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
> [email protected]> wrote:
>
>>
>>
>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
>> wrote:
>>
>>> Hi Khushboo,
>>>
>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>> [email protected]> wrote:
>>>
>>>> Sahil, once the issues get resolved, please raise the PR and we will do
>>>> the final review there.
>>>>
>>> Could you please tell me to which branch I should raise the PR?
>>>
>> Master branch
>>
>>> Also, should I remove the code responsible for the static DonutChart of
>>> process information?
>>>
>>> Thank you,
>>> Sahil
>>>
>>>>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-31 10:47 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-08-31 10:47 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers; Khushboo Vashi <[email protected]>; Akshay Joshi <[email protected]>; Dave Page <[email protected]>; Ashesh Vashi <[email protected]>
Hi Aditya,
I have made all these changes except the StreamingChart issue. I tried
filling an array with null values inside the StreamingChart component while
initializing initialState but still the issue is not resolved.
I have made following changes:
> const initialState = [
> Array.from(new Array(xRange).keys()),
> ...(data.datasets?.map((d)=>{
> let nullValues = new Array(xRange - d.data.length).fill(null);
> let ret = [...nullValues, ...d.data];
> ret.reverse();
> return ret;
> })??{}),
> ];
It works fine if we initialize the data array with the null values but I'm
not getting why this is not working.
Thank you,
Sahil
[image: Mailtrack]
<https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...;
Sender
notified by
Mailtrack
<https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...;
31/08/23,
16:14:27
On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> I have few observations. You have added separate titles for graphs and
> other tabular data. This is inconsistent with existing UI.
> For example,
> [image: image.png]
> like here:
> [image: image.png]
>
> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
> like here:
> [image: image.png]
>
> [image: image.png]
>
> [image: image.png]
>
> The dashboard goes blank when I change refresh rates.
>
> [image: image.png]
>
> And regarding filling with nulls to fix the reversing issue of graph - you
> can do it in StreamingChart itself when setting initialState var, as it is
> StreamingChart's responsibility to do it.
> Next review will be on the PR directly.
>
>
> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <[email protected]>
> wrote:
>
>> Hello everyone,
>>
>> I have raised the PR <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
>> I would like to request you all to review the changes and provide your
>> valuable feedback. Your insights and suggestions would be invaluable in
>> ensuring the quality and accuracy of the codebase.
>>
>> Thank you,
>> Sahil
>>
>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>> [email protected]> wrote:
>>
>>>
>>>
>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
>>> wrote:
>>>
>>>> Hi Khushboo,
>>>>
>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>> Sahil, once the issues get resolved, please raise the PR and we will
>>>>> do the final review there.
>>>>>
>>>> Could you please tell me to which branch I should raise the PR?
>>>>
>>> Master branch
>>>
>>>> Also, should I remove the code responsible for the static DonutChart of
>>>> process information?
>>>>
>>>> Thank you,
>>>> Sahil
>>>>
>>>>>
>
> --
> Thanks,
> Aditya Toshniwal
> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
> <https://www.enterprisedb.com/;
> "Don't Complain about Heat, Plant a TREE"
>
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-08-31 11:49 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-08-31 11:49 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Khushboo Vashi <[email protected]>; Akshay Joshi <[email protected]>; Dave Page <[email protected]>; Ashesh Vashi <[email protected]>
Hi Sahil,
OK fine. We will check it later. Not priority. Please also fix the review
raised on PR.
On Thu, Aug 31, 2023, 16:17 Sahil Harpal <[email protected]> wrote:
> Hi Aditya,
> I have made all these changes except the StreamingChart issue. I tried
> filling an array with null values inside the StreamingChart component while
> initializing initialState but still the issue is not resolved.
> I have made following changes:
>
>> const initialState = [
>> Array.from(new Array(xRange).keys()),
>> ...(data.datasets?.map((d)=>{
>> let nullValues = new Array(xRange - d.data.length).fill(null);
>> let ret = [...nullValues, ...d.data];
>> ret.reverse();
>> return ret;
>> })??{}),
>> ];
>
>
> It works fine if we initialize the data array with the null values but I'm
> not getting why this is not working.
>
> Thank you,
> Sahil
>
>
> [image: Mailtrack]
> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
> notified by
> Mailtrack
> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 31/08/23,
> 16:14:27
>
> On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>>
>> I have few observations. You have added separate titles for graphs and
>> other tabular data. This is inconsistent with existing UI.
>> For example,
>> [image: image.png]
>> like here:
>> [image: image.png]
>>
>> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
>> like here:
>> [image: image.png]
>>
>> [image: image.png]
>>
>> [image: image.png]
>>
>> The dashboard goes blank when I change refresh rates.
>>
>> [image: image.png]
>>
>> And regarding filling with nulls to fix the reversing issue of graph -
>> you can do it in StreamingChart itself when setting initialState var, as it
>> is StreamingChart's responsibility to do it.
>> Next review will be on the PR directly.
>>
>>
>> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hello everyone,
>>>
>>> I have raised the PR <https://github.com/pgadmin-org/pgadmin4/pull/6721;
>>> .
>>> I would like to request you all to review the changes and provide your
>>> valuable feedback. Your insights and suggestions would be invaluable in
>>> ensuring the quality and accuracy of the codebase.
>>>
>>> Thank you,
>>> Sahil
>>>
>>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>>> [email protected]> wrote:
>>>
>>>>
>>>>
>>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
>>>> wrote:
>>>>
>>>>> Hi Khushboo,
>>>>>
>>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Sahil, once the issues get resolved, please raise the PR and we will
>>>>>> do the final review there.
>>>>>>
>>>>> Could you please tell me to which branch I should raise the PR?
>>>>>
>>>> Master branch
>>>>
>>>>> Also, should I remove the code responsible for the static DonutChart
>>>>> of process information?
>>>>>
>>>>> Thank you,
>>>>> Sahil
>>>>>
>>>>>>
>>
>> --
>> Thanks,
>> Aditya Toshniwal
>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>> <https://www.enterprisedb.com/;
>> "Don't Complain about Heat, Plant a TREE"
>>
>
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-02 21:00 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-09-02 21:00 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers; Khushboo Vashi <[email protected]>; Akshay Joshi <[email protected]>; Dave Page <[email protected]>; Ashesh Vashi <[email protected]>
Hi Aditya,
I have made almost all of the requested changes and pushed the latest code.
I just need a bit of clarification for a couple of suggestions that I have
posted in the reviews.
Thank you,
Sahil
On Thu, 31 Aug 2023 at 17:20, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> OK fine. We will check it later. Not priority. Please also fix the review
> raised on PR.
>
> On Thu, Aug 31, 2023, 16:17 Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Aditya,
>> I have made all these changes except the StreamingChart issue. I tried
>> filling an array with null values inside the StreamingChart component while
>> initializing initialState but still the issue is not resolved.
>> I have made following changes:
>>
>>> const initialState = [
>>> Array.from(new Array(xRange).keys()),
>>> ...(data.datasets?.map((d)=>{
>>> let nullValues = new Array(xRange - d.data.length).fill(null);
>>> let ret = [...nullValues, ...d.data];
>>> ret.reverse();
>>> return ret;
>>> })??{}),
>>> ];
>>
>>
>> It works fine if we initialize the data array with the null values but
>> I'm not getting why this is not working.
>>
>> Thank you,
>> Sahil
>>
>>
>> [image: Mailtrack]
>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
>> notified by
>> Mailtrack
>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 31/08/23,
>> 16:14:27
>>
>> On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> Hi Sahil,
>>>
>>> I have few observations. You have added separate titles for graphs and
>>> other tabular data. This is inconsistent with existing UI.
>>> For example,
>>> [image: image.png]
>>> like here:
>>> [image: image.png]
>>>
>>> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
>>> like here:
>>> [image: image.png]
>>>
>>> [image: image.png]
>>>
>>> [image: image.png]
>>>
>>> The dashboard goes blank when I change refresh rates.
>>>
>>> [image: image.png]
>>>
>>> And regarding filling with nulls to fix the reversing issue of graph -
>>> you can do it in StreamingChart itself when setting initialState var, as it
>>> is StreamingChart's responsibility to do it.
>>> Next review will be on the PR directly.
>>>
>>>
>>> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Hello everyone,
>>>>
>>>> I have raised the PR
>>>> <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
>>>> I would like to request you all to review the changes and provide your
>>>> valuable feedback. Your insights and suggestions would be invaluable in
>>>> ensuring the quality and accuracy of the codebase.
>>>>
>>>> Thank you,
>>>> Sahil
>>>>
>>>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>>>> [email protected]> wrote:
>>>>
>>>>>
>>>>>
>>>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
>>>>> wrote:
>>>>>
>>>>>> Hi Khushboo,
>>>>>>
>>>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Sahil, once the issues get resolved, please raise the PR and we will
>>>>>>> do the final review there.
>>>>>>>
>>>>>> Could you please tell me to which branch I should raise the PR?
>>>>>>
>>>>> Master branch
>>>>>
>>>>>> Also, should I remove the code responsible for the static DonutChart
>>>>>> of process information?
>>>>>>
>>>>>> Thank you,
>>>>>> Sahil
>>>>>>
>>>>>>>
>>>
>>> --
>>> Thanks,
>>> Aditya Toshniwal
>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>> <https://www.enterprisedb.com/;
>>> "Don't Complain about Heat, Plant a TREE"
>>>
>>
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-04 05:47 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-09-04 05:47 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers; Khushboo Vashi <[email protected]>; Akshay Joshi <[email protected]>; Dave Page <[email protected]>; Ashesh Vashi <[email protected]>
Hi Sahil,
I have replied to the PR.
On Sun, Sep 3, 2023 at 2:31 AM Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
>
> I have made almost all of the requested changes and pushed the latest
> code. I just need a bit of clarification for a couple of suggestions that I
> have posted in the reviews.
>
> Thank you,
> Sahil
>
>
> On Thu, 31 Aug 2023 at 17:20, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>>
>> OK fine. We will check it later. Not priority. Please also fix the
>> review raised on PR.
>>
>> On Thu, Aug 31, 2023, 16:17 Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hi Aditya,
>>> I have made all these changes except the StreamingChart issue. I tried
>>> filling an array with null values inside the StreamingChart component while
>>> initializing initialState but still the issue is not resolved.
>>> I have made following changes:
>>>
>>>> const initialState = [
>>>> Array.from(new Array(xRange).keys()),
>>>> ...(data.datasets?.map((d)=>{
>>>> let nullValues = new Array(xRange - d.data.length).fill(null);
>>>> let ret = [...nullValues, ...d.data];
>>>> ret.reverse();
>>>> return ret;
>>>> })??{}),
>>>> ];
>>>
>>>
>>> It works fine if we initialize the data array with the null values but
>>> I'm not getting why this is not working.
>>>
>>> Thank you,
>>> Sahil
>>>
>>>
>>> [image: Mailtrack]
>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
>>> notified by
>>> Mailtrack
>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 31/08/23,
>>> 16:14:27
>>>
>>> On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil,
>>>>
>>>> I have few observations. You have added separate titles for graphs and
>>>> other tabular data. This is inconsistent with existing UI.
>>>> For example,
>>>> [image: image.png]
>>>> like here:
>>>> [image: image.png]
>>>>
>>>> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
>>>> like here:
>>>> [image: image.png]
>>>>
>>>> [image: image.png]
>>>>
>>>> [image: image.png]
>>>>
>>>> The dashboard goes blank when I change refresh rates.
>>>>
>>>> [image: image.png]
>>>>
>>>> And regarding filling with nulls to fix the reversing issue of graph -
>>>> you can do it in StreamingChart itself when setting initialState var, as it
>>>> is StreamingChart's responsibility to do it.
>>>> Next review will be on the PR directly.
>>>>
>>>>
>>>> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Hello everyone,
>>>>>
>>>>> I have raised the PR
>>>>> <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
>>>>> I would like to request you all to review the changes and provide your
>>>>> valuable feedback. Your insights and suggestions would be invaluable in
>>>>> ensuring the quality and accuracy of the codebase.
>>>>>
>>>>> Thank you,
>>>>> Sahil
>>>>>
>>>>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>>
>>>>>>
>>>>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
>>>>>> wrote:
>>>>>>
>>>>>>> Hi Khushboo,
>>>>>>>
>>>>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Sahil, once the issues get resolved, please raise the PR and we
>>>>>>>> will do the final review there.
>>>>>>>>
>>>>>>> Could you please tell me to which branch I should raise the PR?
>>>>>>>
>>>>>> Master branch
>>>>>>
>>>>>>> Also, should I remove the code responsible for the static DonutChart
>>>>>>> of process information?
>>>>>>>
>>>>>>> Thank you,
>>>>>>> Sahil
>>>>>>>
>>>>>>>>
>>>>
>>>> --
>>>> Thanks,
>>>> Aditya Toshniwal
>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>> <https://www.enterprisedb.com/;
>>>> "Don't Complain about Heat, Plant a TREE"
>>>>
>>>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-07 19:43 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-09-07 19:43 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers
Hi Aditya,
Sorry for the delay; I've been a bit busy lately. I have made all the
requested changes. Could you please review it?
Thanks,
Sahil
On Mon, 4 Sept 2023 at 11:17, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> I have replied to the PR.
>
> On Sun, Sep 3, 2023 at 2:31 AM Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Aditya,
>>
>> I have made almost all of the requested changes and pushed the latest
>> code. I just need a bit of clarification for a couple of suggestions that I
>> have posted in the reviews.
>>
>> Thank you,
>> Sahil
>>
>>
>> On Thu, 31 Aug 2023 at 17:20, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> Hi Sahil,
>>>
>>> OK fine. We will check it later. Not priority. Please also fix the
>>> review raised on PR.
>>>
>>> On Thu, Aug 31, 2023, 16:17 Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Hi Aditya,
>>>> I have made all these changes except the StreamingChart issue. I tried
>>>> filling an array with null values inside the StreamingChart component while
>>>> initializing initialState but still the issue is not resolved.
>>>> I have made following changes:
>>>>
>>>>> const initialState = [
>>>>> Array.from(new Array(xRange).keys()),
>>>>> ...(data.datasets?.map((d)=>{
>>>>> let nullValues = new Array(xRange - d.data.length).fill(null);
>>>>> let ret = [...nullValues, ...d.data];
>>>>> ret.reverse();
>>>>> return ret;
>>>>> })??{}),
>>>>> ];
>>>>
>>>>
>>>> It works fine if we initialize the data array with the null values but
>>>> I'm not getting why this is not working.
>>>>
>>>> Thank you,
>>>> Sahil
>>>>
>>>>
>>>> [image: Mailtrack]
>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
>>>> notified by
>>>> Mailtrack
>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 31/08/23,
>>>> 16:14:27
>>>>
>>>> On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Sahil,
>>>>>
>>>>> I have few observations. You have added separate titles for graphs and
>>>>> other tabular data. This is inconsistent with existing UI.
>>>>> For example,
>>>>> [image: image.png]
>>>>> like here:
>>>>> [image: image.png]
>>>>>
>>>>> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
>>>>> like here:
>>>>> [image: image.png]
>>>>>
>>>>> [image: image.png]
>>>>>
>>>>> [image: image.png]
>>>>>
>>>>> The dashboard goes blank when I change refresh rates.
>>>>>
>>>>> [image: image.png]
>>>>>
>>>>> And regarding filling with nulls to fix the reversing issue of graph -
>>>>> you can do it in StreamingChart itself when setting initialState var, as it
>>>>> is StreamingChart's responsibility to do it.
>>>>> Next review will be on the PR directly.
>>>>>
>>>>>
>>>>> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hello everyone,
>>>>>>
>>>>>> I have raised the PR
>>>>>> <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
>>>>>> I would like to request you all to review the changes and provide
>>>>>> your valuable feedback. Your insights and suggestions would be invaluable
>>>>>> in ensuring the quality and accuracy of the codebase.
>>>>>>
>>>>>> Thank you,
>>>>>> Sahil
>>>>>>
>>>>>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
>>>>>>> wrote:
>>>>>>>
>>>>>>>> Hi Khushboo,
>>>>>>>>
>>>>>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Sahil, once the issues get resolved, please raise the PR and we
>>>>>>>>> will do the final review there.
>>>>>>>>>
>>>>>>>> Could you please tell me to which branch I should raise the PR?
>>>>>>>>
>>>>>>> Master branch
>>>>>>>
>>>>>>>> Also, should I remove the code responsible for the static
>>>>>>>> DonutChart of process information?
>>>>>>>>
>>>>>>>> Thank you,
>>>>>>>> Sahil
>>>>>>>>
>>>>>>>>>
>>>>>
>>>>> --
>>>>> Thanks,
>>>>> Aditya Toshniwal
>>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>>> <https://www.enterprisedb.com/;
>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>
>>>>
>
> --
> Thanks,
> Aditya Toshniwal
> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
> <https://www.enterprisedb.com/;
> "Don't Complain about Heat, Plant a TREE"
>
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-08 03:33 Aditya Toshniwal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Aditya Toshniwal @ 2023-09-08 03:33 UTC (permalink / raw)
To: Sahil Harpal <[email protected]>; +Cc: pgadmin-hackers
Hi Sahil,
Your PR is showing 135 file changes and a lot of commits which shouldn't
have appeared on your PR.
It is very difficult to identify your changes. Can you please check once?
On Fri, Sep 8, 2023 at 1:13 AM Sahil Harpal <[email protected]>
wrote:
> Hi Aditya,
>
> Sorry for the delay; I've been a bit busy lately. I have made all the
> requested changes. Could you please review it?
>
> Thanks,
> Sahil
>
> On Mon, 4 Sept 2023 at 11:17, Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>>
>> I have replied to the PR.
>>
>> On Sun, Sep 3, 2023 at 2:31 AM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hi Aditya,
>>>
>>> I have made almost all of the requested changes and pushed the latest
>>> code. I just need a bit of clarification for a couple of suggestions that I
>>> have posted in the reviews.
>>>
>>> Thank you,
>>> Sahil
>>>
>>>
>>> On Thu, 31 Aug 2023 at 17:20, Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil,
>>>>
>>>> OK fine. We will check it later. Not priority. Please also fix the
>>>> review raised on PR.
>>>>
>>>> On Thu, Aug 31, 2023, 16:17 Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Hi Aditya,
>>>>> I have made all these changes except the StreamingChart issue. I tried
>>>>> filling an array with null values inside the StreamingChart component while
>>>>> initializing initialState but still the issue is not resolved.
>>>>> I have made following changes:
>>>>>
>>>>>> const initialState = [
>>>>>> Array.from(new Array(xRange).keys()),
>>>>>> ...(data.datasets?.map((d)=>{
>>>>>> let nullValues = new Array(xRange - d.data.length).fill(null);
>>>>>> let ret = [...nullValues, ...d.data];
>>>>>> ret.reverse();
>>>>>> return ret;
>>>>>> })??{}),
>>>>>> ];
>>>>>
>>>>>
>>>>> It works fine if we initialize the data array with the null values but
>>>>> I'm not getting why this is not working.
>>>>>
>>>>> Thank you,
>>>>> Sahil
>>>>>
>>>>>
>>>>> [image: Mailtrack]
>>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
>>>>> notified by
>>>>> Mailtrack
>>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 31/08/23,
>>>>> 16:14:27
>>>>>
>>>>> On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Sahil,
>>>>>>
>>>>>> I have few observations. You have added separate titles for graphs
>>>>>> and other tabular data. This is inconsistent with existing UI.
>>>>>> For example,
>>>>>> [image: image.png]
>>>>>> like here:
>>>>>> [image: image.png]
>>>>>>
>>>>>> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
>>>>>> like here:
>>>>>> [image: image.png]
>>>>>>
>>>>>> [image: image.png]
>>>>>>
>>>>>> [image: image.png]
>>>>>>
>>>>>> The dashboard goes blank when I change refresh rates.
>>>>>>
>>>>>> [image: image.png]
>>>>>>
>>>>>> And regarding filling with nulls to fix the reversing issue of graph
>>>>>> - you can do it in StreamingChart itself when setting initialState var, as
>>>>>> it is StreamingChart's responsibility to do it.
>>>>>> Next review will be on the PR directly.
>>>>>>
>>>>>>
>>>>>> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hello everyone,
>>>>>>>
>>>>>>> I have raised the PR
>>>>>>> <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
>>>>>>> I would like to request you all to review the changes and provide
>>>>>>> your valuable feedback. Your insights and suggestions would be invaluable
>>>>>>> in ensuring the quality and accuracy of the codebase.
>>>>>>>
>>>>>>> Thank you,
>>>>>>> Sahil
>>>>>>>
>>>>>>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <[email protected]>
>>>>>>>> wrote:
>>>>>>>>
>>>>>>>>> Hi Khushboo,
>>>>>>>>>
>>>>>>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Sahil, once the issues get resolved, please raise the PR and we
>>>>>>>>>> will do the final review there.
>>>>>>>>>>
>>>>>>>>> Could you please tell me to which branch I should raise the PR?
>>>>>>>>>
>>>>>>>> Master branch
>>>>>>>>
>>>>>>>>> Also, should I remove the code responsible for the static
>>>>>>>>> DonutChart of process information?
>>>>>>>>>
>>>>>>>>> Thank you,
>>>>>>>>> Sahil
>>>>>>>>>
>>>>>>>>>>
>>>>>>
>>>>>> --
>>>>>> Thanks,
>>>>>> Aditya Toshniwal
>>>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>>>> <https://www.enterprisedb.com/;
>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>
>>>>>
>>
>> --
>> Thanks,
>> Aditya Toshniwal
>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>> <https://www.enterprisedb.com/;
>> "Don't Complain about Heat, Plant a TREE"
>>
>
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
<https://www.enterprisedb.com/;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-08 05:20 Sahil Harpal <[email protected]>
parent: Aditya Toshniwal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-09-08 05:20 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers
Oh yeah! Maybe because I rebased the branch. I'll try to fix this.
On Fri, Sep 8, 2023, 9:04 AM Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> Your PR is showing 135 file changes and a lot of commits which shouldn't
> have appeared on your PR.
> It is very difficult to identify your changes. Can you please check once?
>
> On Fri, Sep 8, 2023 at 1:13 AM Sahil Harpal <[email protected]>
> wrote:
>
>> Hi Aditya,
>>
>> Sorry for the delay; I've been a bit busy lately. I have made all the
>> requested changes. Could you please review it?
>>
>> Thanks,
>> Sahil
>>
>> On Mon, 4 Sept 2023 at 11:17, Aditya Toshniwal <
>> [email protected]> wrote:
>>
>>> Hi Sahil,
>>>
>>> I have replied to the PR.
>>>
>>> On Sun, Sep 3, 2023 at 2:31 AM Sahil Harpal <[email protected]>
>>> wrote:
>>>
>>>> Hi Aditya,
>>>>
>>>> I have made almost all of the requested changes and pushed the latest
>>>> code. I just need a bit of clarification for a couple of suggestions that I
>>>> have posted in the reviews.
>>>>
>>>> Thank you,
>>>> Sahil
>>>>
>>>>
>>>> On Thu, 31 Aug 2023 at 17:20, Aditya Toshniwal <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Sahil,
>>>>>
>>>>> OK fine. We will check it later. Not priority. Please also fix the
>>>>> review raised on PR.
>>>>>
>>>>> On Thu, Aug 31, 2023, 16:17 Sahil Harpal <[email protected]>
>>>>> wrote:
>>>>>
>>>>>> Hi Aditya,
>>>>>> I have made all these changes except the StreamingChart issue. I
>>>>>> tried filling an array with null values inside the StreamingChart component
>>>>>> while initializing initialState but still the issue is not resolved.
>>>>>> I have made following changes:
>>>>>>
>>>>>>> const initialState = [
>>>>>>> Array.from(new Array(xRange).keys()),
>>>>>>> ...(data.datasets?.map((d)=>{
>>>>>>> let nullValues = new Array(xRange - d.data.length).fill(null);
>>>>>>> let ret = [...nullValues, ...d.data];
>>>>>>> ret.reverse();
>>>>>>> return ret;
>>>>>>> })??{}),
>>>>>>> ];
>>>>>>
>>>>>>
>>>>>> It works fine if we initialize the data array with the null values
>>>>>> but I'm not getting why this is not working.
>>>>>>
>>>>>> Thank you,
>>>>>> Sahil
>>>>>>
>>>>>>
>>>>>> [image: Mailtrack]
>>>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
>>>>>> notified by
>>>>>> Mailtrack
>>>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 31/08/23,
>>>>>> 16:14:27
>>>>>>
>>>>>> On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Sahil,
>>>>>>>
>>>>>>> I have few observations. You have added separate titles for graphs
>>>>>>> and other tabular data. This is inconsistent with existing UI.
>>>>>>> For example,
>>>>>>> [image: image.png]
>>>>>>> like here:
>>>>>>> [image: image.png]
>>>>>>>
>>>>>>> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
>>>>>>> like here:
>>>>>>> [image: image.png]
>>>>>>>
>>>>>>> [image: image.png]
>>>>>>>
>>>>>>> [image: image.png]
>>>>>>>
>>>>>>> The dashboard goes blank when I change refresh rates.
>>>>>>>
>>>>>>> [image: image.png]
>>>>>>>
>>>>>>> And regarding filling with nulls to fix the reversing issue of graph
>>>>>>> - you can do it in StreamingChart itself when setting initialState var, as
>>>>>>> it is StreamingChart's responsibility to do it.
>>>>>>> Next review will be on the PR directly.
>>>>>>>
>>>>>>>
>>>>>>> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hello everyone,
>>>>>>>>
>>>>>>>> I have raised the PR
>>>>>>>> <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
>>>>>>>> I would like to request you all to review the changes and provide
>>>>>>>> your valuable feedback. Your insights and suggestions would be invaluable
>>>>>>>> in ensuring the quality and accuracy of the codebase.
>>>>>>>>
>>>>>>>> Thank you,
>>>>>>>> Sahil
>>>>>>>>
>>>>>>>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi Khushboo,
>>>>>>>>>>
>>>>>>>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Sahil, once the issues get resolved, please raise the PR and we
>>>>>>>>>>> will do the final review there.
>>>>>>>>>>>
>>>>>>>>>> Could you please tell me to which branch I should raise the PR?
>>>>>>>>>>
>>>>>>>>> Master branch
>>>>>>>>>
>>>>>>>>>> Also, should I remove the code responsible for the static
>>>>>>>>>> DonutChart of process information?
>>>>>>>>>>
>>>>>>>>>> Thank you,
>>>>>>>>>> Sahil
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Thanks,
>>>>>>> Aditya Toshniwal
>>>>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>>>>> <https://www.enterprisedb.com/;
>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>
>>>>>>
>>>
>>> --
>>> Thanks,
>>> Aditya Toshniwal
>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>> <https://www.enterprisedb.com/;
>>> "Don't Complain about Heat, Plant a TREE"
>>>
>>
>
> --
> Thanks,
> Aditya Toshniwal
> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
> <https://www.enterprisedb.com/;
> "Don't Complain about Heat, Plant a TREE"
>
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-08 09:39 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-09-08 09:39 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers
Hi Aditya,
I have fixed this.
Thank you,
Sahil
[image: Mailtrack]
<https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...;
Sender
notified by
Mailtrack
<https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...;
08/09/23,
15:08:19
On Fri, 8 Sept 2023 at 10:50, Sahil Harpal <[email protected]>
wrote:
> Oh yeah! Maybe because I rebased the branch. I'll try to fix this.
>
> On Fri, Sep 8, 2023, 9:04 AM Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi Sahil,
>>
>> Your PR is showing 135 file changes and a lot of commits which shouldn't
>> have appeared on your PR.
>> It is very difficult to identify your changes. Can you please check once?
>>
>> On Fri, Sep 8, 2023 at 1:13 AM Sahil Harpal <[email protected]>
>> wrote:
>>
>>> Hi Aditya,
>>>
>>> Sorry for the delay; I've been a bit busy lately. I have made all the
>>> requested changes. Could you please review it?
>>>
>>> Thanks,
>>> Sahil
>>>
>>> On Mon, 4 Sept 2023 at 11:17, Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Sahil,
>>>>
>>>> I have replied to the PR.
>>>>
>>>> On Sun, Sep 3, 2023 at 2:31 AM Sahil Harpal <[email protected]>
>>>> wrote:
>>>>
>>>>> Hi Aditya,
>>>>>
>>>>> I have made almost all of the requested changes and pushed the latest
>>>>> code. I just need a bit of clarification for a couple of suggestions that I
>>>>> have posted in the reviews.
>>>>>
>>>>> Thank you,
>>>>> Sahil
>>>>>
>>>>>
>>>>> On Thu, 31 Aug 2023 at 17:20, Aditya Toshniwal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Sahil,
>>>>>>
>>>>>> OK fine. We will check it later. Not priority. Please also fix the
>>>>>> review raised on PR.
>>>>>>
>>>>>> On Thu, Aug 31, 2023, 16:17 Sahil Harpal <[email protected]>
>>>>>> wrote:
>>>>>>
>>>>>>> Hi Aditya,
>>>>>>> I have made all these changes except the StreamingChart issue. I
>>>>>>> tried filling an array with null values inside the StreamingChart component
>>>>>>> while initializing initialState but still the issue is not resolved.
>>>>>>> I have made following changes:
>>>>>>>
>>>>>>>> const initialState = [
>>>>>>>> Array.from(new Array(xRange).keys()),
>>>>>>>> ...(data.datasets?.map((d)=>{
>>>>>>>> let nullValues = new Array(xRange -
>>>>>>>> d.data.length).fill(null);
>>>>>>>> let ret = [...nullValues, ...d.data];
>>>>>>>> ret.reverse();
>>>>>>>> return ret;
>>>>>>>> })??{}),
>>>>>>>> ];
>>>>>>>
>>>>>>>
>>>>>>> It works fine if we initialize the data array with the null values
>>>>>>> but I'm not getting why this is not working.
>>>>>>>
>>>>>>> Thank you,
>>>>>>> Sahil
>>>>>>>
>>>>>>>
>>>>>>> [image: Mailtrack]
>>>>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; Sender
>>>>>>> notified by
>>>>>>> Mailtrack
>>>>>>> <https://mailtrack.io?utm_source=gmail&utm_medium=signature&utm_campaign=signaturevirality11&...; 31/08/23,
>>>>>>> 16:14:27
>>>>>>>
>>>>>>> On Mon, 28 Aug 2023 at 10:44, Aditya Toshniwal <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Sahil,
>>>>>>>>
>>>>>>>> I have few observations. You have added separate titles for graphs
>>>>>>>> and other tabular data. This is inconsistent with existing UI.
>>>>>>>> For example,
>>>>>>>> [image: image.png]
>>>>>>>> like here:
>>>>>>>> [image: image.png]
>>>>>>>>
>>>>>>>> [image: Screenshot 2023-08-28 at 10.28.59 AM.png]
>>>>>>>> like here:
>>>>>>>> [image: image.png]
>>>>>>>>
>>>>>>>> [image: image.png]
>>>>>>>>
>>>>>>>> [image: image.png]
>>>>>>>>
>>>>>>>> The dashboard goes blank when I change refresh rates.
>>>>>>>>
>>>>>>>> [image: image.png]
>>>>>>>>
>>>>>>>> And regarding filling with nulls to fix the reversing issue of
>>>>>>>> graph - you can do it in StreamingChart itself when setting initialState
>>>>>>>> var, as it is StreamingChart's responsibility to do it.
>>>>>>>> Next review will be on the PR directly.
>>>>>>>>
>>>>>>>>
>>>>>>>> On Sun, Aug 27, 2023 at 5:58 PM Sahil Harpal <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hello everyone,
>>>>>>>>>
>>>>>>>>> I have raised the PR
>>>>>>>>> <https://github.com/pgadmin-org/pgadmin4/pull/6721;.
>>>>>>>>> I would like to request you all to review the changes and provide
>>>>>>>>> your valuable feedback. Your insights and suggestions would be invaluable
>>>>>>>>> in ensuring the quality and accuracy of the codebase.
>>>>>>>>>
>>>>>>>>> Thank you,
>>>>>>>>> Sahil
>>>>>>>>>
>>>>>>>>> On Sun, 27 Aug 2023 at 07:13, Khushboo Vashi <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On Sat, 26 Aug 2023, 11:36 Sahil Harpal, <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi Khushboo,
>>>>>>>>>>>
>>>>>>>>>>> On Mon, 21 Aug 2023 at 10:03, Khushboo Vashi <
>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Sahil, once the issues get resolved, please raise the PR and we
>>>>>>>>>>>> will do the final review there.
>>>>>>>>>>>>
>>>>>>>>>>> Could you please tell me to which branch I should raise the PR?
>>>>>>>>>>>
>>>>>>>>>> Master branch
>>>>>>>>>>
>>>>>>>>>>> Also, should I remove the code responsible for the static
>>>>>>>>>>> DonutChart of process information?
>>>>>>>>>>>
>>>>>>>>>>> Thank you,
>>>>>>>>>>> Sahil
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Thanks,
>>>>>>>> Aditya Toshniwal
>>>>>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>>>>>> <https://www.enterprisedb.com/;
>>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>>
>>>>>>>
>>>>
>>>> --
>>>> Thanks,
>>>> Aditya Toshniwal
>>>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>>>> <https://www.enterprisedb.com/;
>>>> "Don't Complain about Heat, Plant a TREE"
>>>>
>>>
>>
>> --
>> Thanks,
>> Aditya Toshniwal
>> pgAdmin Hacker | Sr. Software Architect | *enterprisedb.com*
>> <https://www.enterprisedb.com/;
>> "Don't Complain about Heat, Plant a TREE"
>>
>
Attachments:
[image/png] image.png (62.5K, 3-image.png)
download | view image
[image/png] image.png (28.3K, 4-image.png)
download | view image
[image/png] Screenshot 2023-08-28 at 10.28.59 AM.png (93.6K, 5-Screenshot%202023-08-28%20at%2010.28.59%20AM.png)
download | view image
[image/png] image.png (21.2K, 6-image.png)
download | view image
[image/png] image.png (82.4K, 7-image.png)
download | view image
[image/png] image.png (193.0K, 8-image.png)
download | view image
[image/png] image.png (124.6K, 9-image.png)
download | view image
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-13 04:51 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 1 reply; 106+ messages in thread
From: Sahil Harpal @ 2023-09-13 04:51 UTC (permalink / raw)
To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers
Hello Aditya,
On Fri, 8 Sept 2023 at 09:04, Aditya Toshniwal <
[email protected]> wrote:
> Hi Sahil,
>
> Your PR is showing 135 file changes and a lot of commits which shouldn't
> have appeared on your PR.
> It is very difficult to identify your changes. Can you please check once?
>
I have resolved the issue with the extra commits, and now there are only 9
files that I have updated.
Please let me know if there is anything else required from my side.
Thank you,
Sahil
^ permalink raw reply [nested|flat] 106+ messages in thread
* Re: Pgadmin4 System Stats Extension Design
@ 2023-09-15 08:18 Sahil Harpal <[email protected]>
parent: Sahil Harpal <[email protected]>
0 siblings, 0 replies; 106+ messages in thread
From: Sahil Harpal @ 2023-09-15 08:18 UTC (permalink / raw)
To: Khushboo Vashi <[email protected]>; +Cc: pgadmin-hackers
Hi Khushboo,
Could you please suggest labels for the tables in the Memory and CPU tabs.
Also, I'm not able to reproduce the alignment issue that you have
mentioned, but I have added a fix to by setting width to 100% maybe it
would work.
Thank you,
Sahil
^ permalink raw reply [nested|flat] 106+ messages in thread
end of thread, other threads:[~2023-09-15 08:18 UTC | newest]
Thread overview: 106+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-06-13 09:59 Pgadmin4 System Stats Extension Design Sahil Harpal <[email protected]>
2023-06-14 04:42 ` Akshay Joshi <[email protected]>
2023-06-14 20:11 ` Sahil Harpal <[email protected]>
2023-06-15 05:37 ` Akshay Joshi <[email protected]>
2023-06-15 05:51 ` Aditya Toshniwal <[email protected]>
2023-06-15 08:55 ` Sahil Harpal <[email protected]>
2023-06-15 09:52 ` Dave Page <[email protected]>
2023-06-15 10:07 ` Khushboo Vashi <[email protected]>
2023-06-15 10:57 ` Dave Page <[email protected]>
2023-06-16 08:45 ` Sahil Harpal <[email protected]>
2023-06-19 14:29 ` Sahil Harpal <[email protected]>
2023-06-19 14:33 ` Dave Page <[email protected]>
2023-06-17 09:01 ` Sahil Harpal <[email protected]>
2023-06-19 08:37 ` Dave Page <[email protected]>
2023-06-19 08:49 ` Ashesh Vashi <[email protected]>
2023-06-19 15:11 ` Sahil Harpal <[email protected]>
2023-06-19 15:20 ` Dave Page <[email protected]>
2023-06-19 20:01 ` Sahil Harpal <[email protected]>
2023-06-20 09:39 ` Dave Page <[email protected]>
2023-06-24 21:01 ` Sahil Harpal <[email protected]>
2023-06-24 21:32 ` Dave Page <[email protected]>
2023-06-26 09:21 ` Sahil Harpal <[email protected]>
2023-07-11 06:28 ` Sahil Harpal <[email protected]>
2023-07-11 06:34 ` Ashesh Vashi <[email protected]>
2023-07-11 08:59 ` Sahil Harpal <[email protected]>
2023-07-11 09:07 ` Aditya Toshniwal <[email protected]>
2023-07-11 15:15 ` Sahil Harpal <[email protected]>
2023-07-12 04:56 ` Ashesh Vashi <[email protected]>
2023-07-12 10:49 ` Sahil Harpal <[email protected]>
2023-07-12 11:03 ` Ashesh Vashi <[email protected]>
2023-07-20 20:29 ` Sahil Harpal <[email protected]>
2023-07-12 05:17 ` Khushboo Vashi <[email protected]>
2023-07-12 10:28 ` Sahil Harpal <[email protected]>
2023-07-20 20:32 ` Sahil Harpal <[email protected]>
2023-07-23 20:00 ` Sahil Harpal <[email protected]>
2023-07-24 05:26 ` Ashesh Vashi <[email protected]>
2023-07-24 09:29 ` Sahil Harpal <[email protected]>
2023-07-24 10:04 ` Dave Page <[email protected]>
2023-07-24 10:47 ` Sahil Harpal <[email protected]>
2023-07-24 11:09 ` Dave Page <[email protected]>
2023-07-25 08:51 ` Sahil Harpal <[email protected]>
2023-07-27 07:39 ` Sahil Harpal <[email protected]>
2023-07-27 08:29 ` Khushboo Vashi <[email protected]>
2023-07-27 10:56 ` Khushboo Vashi <[email protected]>
2023-08-02 12:43 ` Sahil Harpal <[email protected]>
2023-08-03 04:02 ` Aditya Toshniwal <[email protected]>
2023-08-06 07:51 ` Sahil Harpal <[email protected]>
2023-08-07 05:30 ` Aditya Toshniwal <[email protected]>
2023-08-07 11:17 ` Sahil Harpal <[email protected]>
2023-08-07 11:41 ` Aditya Toshniwal <[email protected]>
2023-08-09 19:07 ` Sahil Harpal <[email protected]>
2023-08-16 02:59 ` Sahil Harpal <[email protected]>
2023-08-16 04:18 ` Khushboo Vashi <[email protected]>
2023-08-16 05:38 ` Sahil Harpal <[email protected]>
2023-08-16 05:43 ` Khushboo Vashi <[email protected]>
2023-08-16 06:21 ` Sahil Harpal <[email protected]>
2023-08-16 09:55 ` Sahil Harpal <[email protected]>
2023-08-17 05:09 ` Khushboo Vashi <[email protected]>
2023-08-17 06:18 ` Sahil Harpal <[email protected]>
2023-08-17 09:09 ` Khushboo Vashi <[email protected]>
2023-08-17 09:20 ` Sahil Harpal <[email protected]>
2023-08-17 09:23 ` Khushboo Vashi <[email protected]>
2023-08-17 09:35 ` Sahil Harpal <[email protected]>
2023-08-17 09:38 ` Dave Page <[email protected]>
2023-08-17 09:47 ` Sahil Harpal <[email protected]>
2023-08-17 10:12 ` Sahil Harpal <[email protected]>
2023-08-17 12:27 ` Khushboo Vashi <[email protected]>
2023-08-18 04:38 ` Sahil Harpal <[email protected]>
2023-08-19 10:51 ` Sahil Harpal <[email protected]>
2023-08-21 04:29 ` Khushboo Vashi <[email protected]>
2023-08-21 04:32 ` Khushboo Vashi <[email protected]>
2023-08-26 06:06 ` Sahil Harpal <[email protected]>
2023-08-27 01:43 ` Khushboo Vashi <[email protected]>
2023-08-27 12:27 ` Sahil Harpal <[email protected]>
2023-08-28 05:13 ` Aditya Toshniwal <[email protected]>
2023-08-31 10:47 ` Sahil Harpal <[email protected]>
2023-08-31 11:49 ` Aditya Toshniwal <[email protected]>
2023-09-02 21:00 ` Sahil Harpal <[email protected]>
2023-09-04 05:47 ` Aditya Toshniwal <[email protected]>
2023-09-07 19:43 ` Sahil Harpal <[email protected]>
2023-09-08 03:33 ` Aditya Toshniwal <[email protected]>
2023-09-08 05:20 ` Sahil Harpal <[email protected]>
2023-09-08 09:39 ` Sahil Harpal <[email protected]>
2023-09-13 04:51 ` Sahil Harpal <[email protected]>
2023-09-15 08:18 ` Sahil Harpal <[email protected]>
2023-08-22 18:04 ` Sahil Harpal <[email protected]>
2023-08-22 19:13 ` Sahil Harpal <[email protected]>
2023-08-23 06:21 ` Khushboo Vashi <[email protected]>
2023-08-24 14:37 ` Sahil Harpal <[email protected]>
2023-08-25 05:59 ` Aditya Toshniwal <[email protected]>
2023-08-25 06:32 ` Sahil Harpal <[email protected]>
2023-08-25 06:36 ` Aditya Toshniwal <[email protected]>
2023-08-25 06:45 ` Sahil Harpal <[email protected]>
2023-08-25 07:02 ` Aditya Toshniwal <[email protected]>
2023-08-25 08:04 ` Sahil Harpal <[email protected]>
2023-08-25 08:11 ` Sahil Harpal <[email protected]>
2023-08-23 06:07 ` Khushboo Vashi <[email protected]>
2023-08-21 04:25 ` Khushboo Vashi <[email protected]>
2023-08-21 05:21 ` Sahil Harpal <[email protected]>
2023-08-22 07:48 ` Sahil Harpal <[email protected]>
2023-08-22 10:32 ` Khushboo Vashi <[email protected]>
2023-08-22 11:15 ` Sahil Harpal <[email protected]>
2023-08-19 10:50 ` Sahil Harpal <[email protected]>
2023-08-24 14:57 ` Sahil Harpal <[email protected]>
2023-08-25 06:00 ` Aditya Toshniwal <[email protected]>
2023-08-25 06:25 ` Aditya Toshniwal <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox