This is an automated email from the ASF dual-hosted git repository.

siyao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 2dc2895fe5 HDDS-7812. Recon UI: Add visualization for container size 
distribution. (#4665)
2dc2895fe5 is described below

commit 2dc2895fe5b3ae4ac55d1299316cd59b00727723
Author: smitajoshi12 <[email protected]>
AuthorDate: Wed May 31 23:30:00 2023 +0530

    HDDS-7812. Recon UI: Add visualization for container size distribution. 
(#4665)
---
 .../webapps/recon/ozone-recon-web/api/db.json      |  18 ++
 .../webapps/recon/ozone-recon-web/api/routes.json  |   1 +
 .../src/views/insights/insights.less               |  24 +++
 .../src/views/insights/insights.tsx                | 229 ++++++++++++++-------
 4 files changed, 201 insertions(+), 71 deletions(-)

diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
index cd9875669f..11017db3d8 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
@@ -1092,6 +1092,24 @@
       "count": 217
     }
   ],
+  "containerCount": [
+    {
+      "containerSize": 1024,
+      "count": 2
+    },
+    {
+      "containerSize": 16384,
+      "count": 3
+    },
+    {
+      "containerSize": 2097152,
+      "count": 4
+    },
+    {
+      "containerSize": 1536870912,
+      "count": 1
+    }
+  ],
   "root": {
     "status": "OK",
     "path": "/",
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json
index e92e7c6dcc..a004830869 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/routes.json
@@ -2,6 +2,7 @@
   "/api/v1/*": "/$1",
   "/containers/:id/keys": "/keys",
   "/utilization/fileCount": "/fileSizeCounts",
+  "/utilization/containerCount": "/containerCount",
   "/namespace/du?path=/&files=true": "/root",
   "/namespace/du?path=/vol:id&files=true": "/volume",
   "/namespace/du?path=/vol:id/&files=true": "/volume",
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.less
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.less
index 5d0d9243a0..a4083cfacb 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.less
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.less
@@ -32,4 +32,28 @@
     display: inline-block;
     width: 300px;
   }
+
+  .legendtext {
+    fill: rgba(0, 0, 0, 0.65) !important;
+    line-height: 1.5;
+    font-size: 15px !important;
+    font-feature-settings: 'tnum', "tnum";
+    font-variant: tabular-nums;
+  }
+
+  .gtitle {
+    font-size: 22px;
+    font-weight: 500 !important;
+    fill: rgba(0, 0, 0, 0.60) !important;
+    font-feature-settings: 'tnum', "tnum";
+    font-variant: tabular-nums;
+  }
+
+  .slicetext {
+    fill: rgba(252, 248, 248, 0.976) !important;
+    font-size: 17px !important;
+    line-height: 1.5;
+    font-feature-settings: 'tnum', "tnum";
+    font-variant: tabular-nums;
+  }  
 }
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx
index 0f330e5297..1ef0fb09ea 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx
@@ -18,7 +18,7 @@
 
 import React from 'react';
 import axios from 'axios';
-import {Icon, Row, Col} from 'antd';
+import {Icon, Row, Col, Tabs} from 'antd';
 import filesize from 'filesize';
 import {showDataFetchError} from 'utils/common';
 import Plot from 'react-plotly.js';
@@ -26,8 +26,9 @@ import * as Plotly from 'plotly.js';
 import {MultiSelect, IOption} from 'components/multiSelect/multiSelect';
 import {ActionMeta, ValueType} from 'react-select';
 import './insights.less';
+const {TabPane} = Tabs;
 
-const size = filesize.partial({standard: 'iec'});
+const size = filesize.partial({standard: 'iec',round: 0});
 
 interface IFileCountResponse {
   volume: string;
@@ -36,10 +37,17 @@ interface IFileCountResponse {
   count: number;
 }
 
+interface IContainerCountResponse {
+  containerSize: number;
+  count: number;
+}
+
 interface IInsightsState {
   isLoading: boolean;
   fileCountsResponse: IFileCountResponse[];
-  plotData: Plotly.Data[];
+  containerCountResponse: IContainerCountResponse[];
+  fileCountData: Plotly.Data[];
+  containerCountData: Plotly.Data[];
   volumeBucketMap: Map<string, Set<string>>;
   selectedVolumes: IOption[];
   selectedBuckets: IOption[];
@@ -64,7 +72,9 @@ export class Insights extends React.Component<Record<string, 
object>, IInsightsS
     this.state = {
       isLoading: false,
       fileCountsResponse: [],
-      plotData: [],
+      containerCountResponse: [],
+      fileCountData: [],
+      containerCountData: [],
       volumeBucketMap: new Map<string, Set<string>>(),
       selectedBuckets: [],
       selectedVolumes: [],
@@ -122,7 +132,7 @@ export class Insights extends 
React.Component<Record<string, object>, IInsightsS
   };
 
   updatePlotData = () => {
-    const {fileCountsResponse, selectedVolumes, selectedBuckets} = this.state;
+    const {fileCountsResponse, selectedVolumes, selectedBuckets, 
containerCountResponse} = this.state;
     // Aggregate counts across volumes & buckets
     if (selectedVolumes && selectedBuckets) {
       let filteredData = fileCountsResponse;
@@ -138,7 +148,7 @@ export class Insights extends 
React.Component<Record<string, object>, IInsightsS
         filteredData = filteredData.filter(item => 
selectedBucketValues.has(item.bucket));
       }
 
-      const xyMap: Map<number, number> = filteredData.reduce(
+      const xyFileCountMap: Map<number, number> = filteredData.reduce(
         (map: Map<number, number>, current) => {
           const fileSize = current.fileSize;
           const oldCount = map.has(fileSize) ? map.get(fileSize)! : 0;
@@ -147,7 +157,7 @@ export class Insights extends 
React.Component<Record<string, object>, IInsightsS
         }, new Map<number, number>());
       // Calculate the previous power of 2 to find the lower bound of the range
       // Ex: for 2048, the lower bound is 1024
-      const xValues = Array.from(xyMap.keys()).map(value => {
+      const xFileCountValues = Array.from(xyFileCountMap.keys()).map(value => {
         const upperbound = size(value);
         const upperboundPower = Math.log2(value);
         // For 1024 which is 2^10, the lowerbound is 0, since we start binning
@@ -155,12 +165,45 @@ export class Insights extends 
React.Component<Record<string, object>, IInsightsS
         const lowerbound = upperboundPower > 10 ? size(2 ** (upperboundPower - 
1)) : size(0);
         return `${lowerbound} - ${upperbound}`;
       });
+      
+      const xyContainerCountMap: Map<number, number> = 
containerCountResponse.reduce(
+        (map: Map<number, number>, current) => {
+          const containerSize = current.containerSize;
+          const oldCount = map.has(containerSize) ? map.get(containerSize)! : 
0;
+          map.set(containerSize, oldCount + current.count);
+          return map;
+        }, new Map<number, number>());
+      // Calculate the previous power of 2 to find the lower bound of the range
+      // Ex: for 2048, the lower bound is 1024
+      const xContainerCountValues = 
Array.from(xyContainerCountMap.keys()).map(value => {
+        const upperbound = size(value);
+        const upperboundPower = Math.log2(value);
+        // For 1024 which is 2^10, the lowerbound is 0, since we start binning
+        // after 2^10
+        const lowerbound = upperboundPower > 10 ? size(2 ** (upperboundPower - 
1)) : size(0);
+        return `${lowerbound} - ${upperbound}`;
+      });
+
+      let keysize = [];
+      keysize = Array.from(xyContainerCountMap.keys()).map(value => {
+        return (size(value) );
+      });
+
       this.setState({
-        plotData: [{
+        fileCountData: [{
           type: 'bar',
-          x: xValues,
-          y: Array.from(xyMap.values()),
+          x: xFileCountValues,
+          y: Array.from(xyFileCountMap.values()),
           name: 'file count'
+        }],
+        containerCountData: [{
+          type: 'pie',
+          hole: 0.2,
+          values: Array.from(xyContainerCountMap.values()),  
+          customdata: Array.from(xyContainerCountMap.values()),
+          labels: xContainerCountValues, 
+          text: keysize,
+          hovertemplate: 'Container Count: %{customdata}<br>Container Size: 
%{text}<extra></extra>'
         }]
       });
     }
@@ -171,8 +214,13 @@ export class Insights extends 
React.Component<Record<string, object>, IInsightsS
     this.setState({
       isLoading: true
     });
-    axios.get('/api/v1/utilization/fileCount').then(response => {
-      const fileCountsResponse: IFileCountResponse[] = response.data;
+
+    axios.all([
+      axios.get('/api/v1/utilization/fileCount'),
+      axios.get('/api/v1/utilization/containerCount')
+    ]).then(axios.spread((fileCountresponse, containerCountresponse) => {
+      const fileCountsResponse: IFileCountResponse[] = fileCountresponse.data;
+      const containerCountResponse: IContainerCountResponse[] = 
containerCountresponse.data;
       // Construct volume -> bucket[] map for populating filters
       // Ex: vol1 -> [bucket1, bucket2], vol2 -> [bucket1]
       const volumeBucketMap: Map<string, Set<string>> = 
fileCountsResponse.reduce(
@@ -199,13 +247,14 @@ export class Insights extends 
React.Component<Record<string, object>, IInsightsS
         isLoading: false,
         volumeBucketMap,
         fileCountsResponse,
+        containerCountResponse,
         volumeOptions
       }, () => {
         this.updatePlotData();
         // Select all volumes by default
         this.handleVolumeChange([allVolumesOption, ...volumeOptions], {action: 
'select-option'});
       });
-    }).catch(error => {
+    })).catch(error => {
       this.setState({
         isLoading: false
       });
@@ -214,70 +263,108 @@ export class Insights extends 
React.Component<Record<string, object>, IInsightsS
   }
 
   render() {
-    const {plotData, isLoading, selectedBuckets, volumeOptions,
-      selectedVolumes, fileCountsResponse, bucketOptions, 
isBucketSelectionDisabled} = this.state;
+    const {fileCountData, isLoading, selectedBuckets, volumeOptions,
+      selectedVolumes, fileCountsResponse, bucketOptions, 
isBucketSelectionDisabled, containerCountResponse, containerCountData} = 
this.state;
     return (
       <div className='insights-container'>
         <div className='page-header'>
           Insights
         </div>
+       
         <div className='content-div'>
-          {isLoading ? <span><Icon type='loading'/> Loading...</span> :
-            ((fileCountsResponse && fileCountsResponse.length > 0) ?
-              <div>
-                <Row>
-                  <Col xs={24} xl={18}>
-                    <Row>
-                      <Col>
-                        <div className='filter-block'>
-                          <h4>Volumes</h4>
-                          <MultiSelect
-                            allowSelectAll
-                            isMulti
-                            className='multi-select-container'
-                            options={volumeOptions}
-                            closeMenuOnSelect={false}
-                            hideSelectedOptions={false}
-                            value={selectedVolumes}
-                            allOption={allVolumesOption}
-                            onChange={this.handleVolumeChange}
-                          />
-                        </div>
-                        <div className='filter-block'>
-                          <h4>Buckets</h4>
-                          <MultiSelect
-                            allowSelectAll
-                            isMulti
-                            className='multi-select-container'
-                            options={bucketOptions}
-                            closeMenuOnSelect={false}
-                            hideSelectedOptions={false}
-                            value={selectedBuckets}
-                            allOption={allBucketsOption}
-                            isDisabled={isBucketSelectionDisabled}
-                            onChange={this.handleBucketChange}
-                          />
-                        </div>
-                      </Col>
-                    </Row>
-                  </Col>
-                </Row>
-                <Row>
-                  <Col>
-                    <Plot
-                      data={plotData}
-                      layout={
-                        {
-                          width: 800,
-                          height: 600,
-                          title: 'File Size Distribution',
-                          showlegend: true
-                        }
-                      }/>
-                  </Col>
-                </Row>
-              </div> :
-              <div>No data to visualize file size distribution. Add files to 
Ozone to see a visualization on file size distribution.</div>)}
+          <Tabs defaultActiveKey='1'>
+            <TabPane key='1' tab={`File Size`}>
+              {
+                <div className='content-div'>
+                  {isLoading ? <span><Icon type='loading'/> Loading...</span> :
+                    ((fileCountsResponse && fileCountsResponse.length > 0) ?
+                      <div>
+                        <Row>
+                          <Col xs={24} xl={18}>
+                            <Row>
+                              <Col>
+                                <div className='filter-block'>
+                                  <h4>Volumes</h4>
+                                  <MultiSelect
+                                    allowSelectAll
+                                    isMulti
+                                    className='multi-select-container'
+                                    options={volumeOptions}
+                                    closeMenuOnSelect={false}
+                                    hideSelectedOptions={false}
+                                    value={selectedVolumes}
+                                    allOption={allVolumesOption}
+                                    onChange={this.handleVolumeChange}
+                                  />
+                                </div>
+                                <div className='filter-block'>
+                                  <h4>Buckets</h4>
+                                  <MultiSelect
+                                    allowSelectAll
+                                    isMulti
+                                    className='multi-select-container'
+                                    options={bucketOptions}
+                                    closeMenuOnSelect={false}
+                                    hideSelectedOptions={false}
+                                    value={selectedBuckets}
+                                    allOption={allBucketsOption}
+                                    isDisabled={isBucketSelectionDisabled}
+                                    onChange={this.handleBucketChange}
+                                  />
+                                </div>
+                              </Col>
+                            </Row>
+                          </Col>
+                        </Row>
+                        <Row>
+                          <Col>
+                            <Plot
+                              data={fileCountData}
+                              layout={
+                                {
+                                  width: 800,
+                                  height: 600,
+                                  title: 'File Size Distribution',
+                                  showlegend: false
+                                }
+                              } />
+                          </Col>
+                        </Row>
+                      </div> :
+                      <div>No data to visualize file size distribution. Add 
files to Ozone to see a visualization on file size distribution.</div>)}
+                </div>
+              }
+            </TabPane>
+            <TabPane key='2' tab={`Container Size`}>
+              {
+                <div className='content-div'>
+                  {isLoading ? <span><Icon type='loading'/> Loading...</span> :
+                    ((containerCountResponse && containerCountResponse.length 
> 0) ?
+                      <div>
+                        <Row>
+                          <Col>
+                            <Plot
+                              data={containerCountData}
+                              layout={
+                                {
+                                  width: 850,
+                                  height: 750,
+                                  font: {
+                                    family: 'Roboto, sans-serif',
+                                    size: 15
+                                  },
+                                  title: 'Container Size Distribution',
+                                  showlegend: true
+                                }
+                              }/>
+                          </Col>
+                        </Row>
+                      </div> :
+                      <div>No data available for container size distribution 
visualization. Add files to Ozone</div>)}
+                </div>
+              }
+            </TabPane>
+          </Tabs>
         </div>
       </div>
     );


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to