Repository: sodafoundation/delfin

Issue:
Add performance collection support to OceanStor driver
**Issue/Feature Description:**
Add performance collection support to OceanStor driver

**Why this issue to fixed / feature is needed(give scenarios or use cases):**

**How to reproduce, in case of a bug:**

**Other Notes /  Environment Information: (Please give the env information, log link or any useful information for this issue)**



Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/sodafoundation/delfin/pull/598?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation) Report
> Merging [#598](https://codecov.io/gh/sodafoundation/delfin/pull/598?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation) (d417460) into [master](https://codecov.io/gh/sodafoundation/delfin/commit/73fe3a1eabae4aa4d8d8158d5e86af9b5393f7be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation) (73fe3a1) will **decrease** coverage by `1.09%`.
> The diff coverage is `25.85%`.


```diff
@@            Coverage Diff             @@
##           master     #598      +/-   ##
==========================================
- Coverage   72.13%   71.04%   -1.10%     
==========================================
  Files         141      141              
  Lines       12211    12260      +49     
  Branches     1446     1469      +23     
==========================================
- Hits         8809     8710      -99     
- Misses       2909     3056     +147     
- Partials      493      494       +1     
```

| [Impacted Files](https://codecov.io/gh/sodafoundation/delfin/pull/598?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation) | Coverage Δ | |
|---|---|---|
| [delfin/exporter/prometheus/prometheus.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2V4cG9ydGVyL3Byb21ldGhldXMvcHJvbWV0aGV1cy5weQ==) | `30.76% <0.00%> (-1.24%)` | :arrow_down: |
| [delfin/drivers/huawei/oceanstor/rest\_client.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2RyaXZlcnMvaHVhd2VpL29jZWFuc3Rvci9yZXN0X2NsaWVudC5weQ==) | `49.05% <11.80%> (-23.84%)` | :arrow_down: |
| [delfin/drivers/huawei/oceanstor/oceanstor.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2RyaXZlcnMvaHVhd2VpL29jZWFuc3Rvci9vY2VhbnN0b3IucHk=) | `75.22% <12.50%> (-4.97%)` | :arrow_down: |
| [delfin/common/constants.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2NvbW1vbi9jb25zdGFudHMucHk=) | `100.00% <100.00%> (ø)` | |
| [delfin/drivers/huawei/oceanstor/consts.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2RyaXZlcnMvaHVhd2VpL29jZWFuc3Rvci9jb25zdHMucHk=) | `100.00% <100.00%> (ø)` | |
| [delfin/drivers/driver.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2RyaXZlcnMvZHJpdmVyLnB5) | `58.13% <0.00%> (-9.14%)` | :arrow_down: |
| [delfin/wsgi/common.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL3dzZ2kvY29tbW9uLnB5) | `64.28% <0.00%> (-3.46%)` | :arrow_down: |
| [delfin/api/validation/validators.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2FwaS92YWxpZGF0aW9uL3ZhbGlkYXRvcnMucHk=) | `44.35% <0.00%> (-1.74%)` | :arrow_down: |
| [delfin/manager.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL21hbmFnZXIucHk=) | `68.42% <0.00%> (-1.58%)` | :arrow_down: |
| [delfin/cryptor.py](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation#diff-ZGVsZmluL2NyeXB0b3IucHk=) | `90.90% <0.00%> (-1.40%)` | :arrow_down: |
| ... and [44 more](https://codecov.io/gh/sodafoundation/delfin/pull/598/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sodafoundation) | |

NajmudheenCT: @joseph-v can you update this PR . please check comments and conflicts

Patch:
diff --git a/delfin/common/constants.py b/delfin/common/constants.py
index ccdb0a5..8a339e6 100644
--- a/delfin/common/constants.py
+++ b/delfin/common/constants.py
@@ -322,6 +322,21 @@ class SecurityLevel(object):
 # Metric model
 metric_struct = namedtuple("Metric", "name labels values")
 
+
+class ResourceType(object):
+    STORAGE = 'storage'
+    STORAGE_POOL = 'storagePool'
+    VOLUME = 'volume'
+    CONTROLLER = 'controller'
+    PORT = 'port'
+    DISK = 'disk'
+    FILESYSTEM = 'filesystem'
+    SHARE = 'share'
+
+    ALL = (STORAGE, STORAGE_POOL, VOLUME, CONTROLLER,
+           PORT, DISK, FILESYSTEM, SHARE)
+
+
 # Unified Array metrics model
 DELFIN_ARRAY_METRICS = [
     "responseTime",
diff --git a/delfin/drivers/huawei/oceanstor/consts.py b/delfin/drivers/huawei/oceanstor/consts.py
index 899acc5..362eb42 100644
--- a/delfin/drivers/huawei/oceanstor/consts.py
+++ b/delfin/drivers/huawei/oceanstor/consts.py
@@ -52,7 +52,6 @@ PORT_RUNNINGSTS_LINKUP = '10'
 PORT_RUNNINGSTS_LINKDOWN = '11'
 PORT_RUNNINGSTS_TOBERECOVERED = '33'
 
-PORT_LOGICTYPE_HOST = '0'
 PORT_LOGICTYPE_EXPANSION = '1'
 PORT_LOGICTYPE_MANAGEMENT = '2'
 PORT_LOGICTYPE_INTERNAL = '3'
@@ -160,3 +159,143 @@ QUERY_PAGE_SIZE = 100
 
 THICK_LUNTYPE = '0'
 THIN_LUNTYPE = '1'
+
+OCEANSTOR_METRICS = {
+    'iops': '22',
+    'readIops': '25',
+    'writeIops': '28',
+    'throughput': '21',
+    'readThroughput': '23',
+    'writeThroughput': '26',
+    'responseTime': '370',
+    'ioSize': '228',
+    'readIoSize': '24',
+    'writeIoSize': '27',
+    'cacheHitRatio': '303',
+    'readCacheHitRatio': '93',
+    'writeCacheHitRatio': '95',
+}
+
+CONVERT_TO_MILLI_SECOND_LIST = [
+    'responseTime'
+]
+
+IOPS_DESCRIPTION = {
+    "unit": "IOPS",
+    "description": "Input/output operations per second"
+}
+READ_IOPS_DESCRIPTION = {
+    "unit": "IOPS",
+    "description": "Read input/output operations per second"
+}
+WRITE_IOPS_DESCRIPTION = {
+    "unit": "IOPS",
+    "description": "Write input/output operations per second"
+}
+THROUGHPUT_DESCRIPTION = {
+    "unit": "MB/s",
+    "description": "Represents how much data is "
+                   "successfully transferred in MB/s"
+}
+READ_THROUGHPUT_DESCRIPTION = {
+    "unit": "MB/s",
+    "description": "Represents how much data read is "
+                   "successfully transferred in MB/s"
+}
+WRITE_THROUGHPUT_DESCRIPTION = {
+    "unit": "MB/s",
+    "description": "Represents how much data write is "
+                   "successfully transferred in MB/s"
+}
+RESPONSE_TIME_DESCRIPTION = {
+    "unit": "ms",
+    "description": "Average time taken for an IO "
+                   "operation in ms"
+}
+CACHE_HIT_RATIO_DESCRIPTION = {
+    "unit": "%",
+    "description": "Percentage of io that are cache hits"
+}
+READ_CACHE_HIT_RATIO_DESCRIPTION = {
+    "unit": "%",
+    "description": "Percentage of read ops that are cache hits"
+}
+WRITE_CACHE_HIT_RATIO_DESCRIPTION = {
+    "unit": "%",
+    "description": "Percentage of write ops that are cache hits"
+}
+IO_SIZE_DESCRIPTION = {
+    "unit": "KB",
+    "description": "The average size of IO requests in KB"
+}
+READ_IO_SIZE_DESCRIPTION = {
+    "unit": "KB",
+    "description": "The average size of read IO requests in KB"
+}
+WRITE_IO_SIZE_DESCRIPTION = {
+    "unit": "KB",
+    "description": "The average size of write IO requests in KB"
+}
+CPU_USAGE_DESCRIPTION = {
+    "unit": "%",
+    "description": "Percentage of CPU usage"
+}
+MEMORY_USAGE_DESCRIPTION = {
+    "unit": "%",
+    "description": "Percentage of DISK memory usage in percentage"
+}
+SERVICE_TIME = {
+    "unit": 'ms',
+    "description": "Service time of the resource in ms"
+}
+POOL_CAP = {
+    "iops": IOPS_DESCRIPTION,
+    "readIops": READ_IOPS_DESCRIPTION,
+    "writeIops": WRITE_IOPS_DESCRIPTION,
+    "throughput": THROUGHPUT_DESCRIPTION,
+    "readThroughput": READ_THROUGHPUT_DESCRIPTION,
+    "writeThroughput": WRITE_THROUGHPUT_DESCRIPTION,
+    "responseTime": RESPONSE_TIME_DESCRIPTION,
+}
+VOLUME_CAP = {
+    "iops": IOPS_DESCRIPTION,
+    "readIops": READ_IOPS_DESCRIPTION,
+    "writeIops": WRITE_IOPS_DESCRIPTION,
+    "throughput": THROUGHPUT_DESCRIPTION,
+    "readThroughput": READ_THROUGHPUT_DESCRIPTION,
+    "writeThroughput": WRITE_THROUGHPUT_DESCRIPTION,
+    "responseTime": RESPONSE_TIME_DESCRIPTION,
+    "cacheHitRatio": CACHE_HIT_RATIO_DESCRIPTION,
+    "readCacheHitRatio": READ_CACHE_HIT_RATIO_DESCRIPTION,
+    "writeCacheHitRatio": WRITE_CACHE_HIT_RATIO_DESCRIPTION,
+    "ioSize": IO_SIZE_DESCRIPTION,
+    "readIoSize": READ_IO_SIZE_DESCRIPTION,
+    "writeIoSize": WRITE_IO_SIZE_DESCRIPTION,
+}
+CONTROLLER_CAP = {
+    "iops": IOPS_DESCRIPTION,
+    "readIops": READ_IOPS_DESCRIPTION,
+    "writeIops": WRITE_IOPS_DESCRIPTION,
+    "throughput": THROUGHPUT_DESCRIPTION,
+    "readThroughput": READ_THROUGHPUT_DESCRIPTION,
+    "writeThroughput": WRITE_THROUGHPUT_DESCRIPTION,
+    "responseTime": RESPONSE_TIME_DESCRIPTION,
+}
+PORT_CAP = {
+    "iops": IOPS_DESCRIPTION,
+    "readIops": READ_IOPS_DESCRIPTION,
+    "writeIops": WRITE_IOPS_DESCRIPTION,
+    "throughput": THROUGHPUT_DESCRIPTION,
+    "readThroughput": READ_THROUGHPUT_DESCRIPTION,
+    "writeThroughput": WRITE_THROUGHPUT_DESCRIPTION,
+    "responseTime": RESPONSE_TIME_DESCRIPTION,
+}
+DISK_CAP = {
+    "iops": IOPS_DESCRIPTION,
+    "readIops": READ_IOPS_DESCRIPTION,
+    "writeIops": WRITE_IOPS_DESCRIPTION,
+    "throughput": THROUGHPUT_DESCRIPTION,
+    "readThroughput": READ_THROUGHPUT_DESCRIPTION,
+    "writeThroughput": WRITE_THROUGHPUT_DESCRIPTION,
+    "responseTime": RESPONSE_TIME_DESCRIPTION,
+}
diff --git a/delfin/drivers/huawei/oceanstor/oceanstor.py b/delfin/drivers/huawei/oceanstor/oceanstor.py
index d512574..85ca041 100644
--- a/delfin/drivers/huawei/oceanstor/oceanstor.py
+++ b/delfin/drivers/huawei/oceanstor/oceanstor.py
@@ -14,6 +14,7 @@
 
 import six
 
+from oslo_config import cfg
 from oslo_log import log
 from delfin.common import constants
 from delfin.drivers.huawei.oceanstor import rest_client, consts, alert_handler
@@ -21,6 +22,17 @@ from delfin.drivers import driver
 from delfin import exception
 
 LOG = log.getLogger(__name__)
+CONF = cfg.CONF
+
+oceanstor_opts = [
+    cfg.StrOpt(
+        'enable_perf_config',
+        default=False,
+        help='Enable changing performance configs on storage array'
+             'Settings for real-time, historical collection updated'),
+]
+
+CONF.register_opts(oceanstor_opts, "oceanstor_driver")
 
 
 class OceanStorDriver(driver.StorageDriver):
@@ -31,6 +43,7 @@ class OceanStorDriver(driver.StorageDriver):
         super().__init__(**kwargs)
         self.client = rest_client.RestClient(**kwargs)
         self.sector_size = consts.SECTORS_SIZE
+        self.init_perf_config = CONF.oceanstor_driver.enable_perf_config
 
     def reset_connection(self, context, **kwargs):
         self.client.reset_connection(**kwargs)
@@ -593,3 +606,89 @@ class OceanStorDriver(driver.StorageDriver):
         alert_model_list = alert_handler.AlertHandler()\
             .parse_queried_alerts(alert_list, query_para)
         return alert_model_list
+
+    def collect_perf_metrics(self, context, storage_id,
+                             resource_metrics, start_time,
+                             end_time):
+        """Collects performance metric for the given interval"""
+        try:
+            if self.init_perf_config:
+                self.client.configure_metrics_collection()
+                self.init_perf_config = False
+
+        except exception.DelfinException as err:
+            err_msg = "Failed to configure collection in OceanStor: %s" %\
+                      (six.text_type(err))
+            LOG.error(err_msg)
+            raise err
+
+        except Exception as err:
+            err_msg = "Failed to configure collection in OceanStor: %s" %\
+                      (six.text_type(err))
+            LOG.error(err_msg)
+            raise exception.InvalidResults(err_msg)
+
+        metrics = []
+        try:
+            # storage-pool metrics
+            if resource_metrics.get(constants.ResourceType.STORAGE_POOL):
+                pool_metrics = self.client.get_pool_metrics(
+                    storage_id,
+                    resource_metrics.get(constants.ResourceType.STORAGE_POOL))
+                metrics.extend(pool_metrics)
+
+            # volume metrics
+            if resource_metrics.get(constants.ResourceType.VOLUME):
+                volume_metrics = self.client.get_volume_metrics(
+                    storage_id,
+                    resource_metrics.get(constants.ResourceType.VOLUME))
+                metrics.extend(volume_metrics)
+
+            # controller metrics
+            if resource_metrics.get(constants.ResourceType.CONTROLLER):
+                controller_metrics = self.client.get_controller_metrics(
+                    storage_id,
+                    resource_metrics.get(constants.ResourceType.CONTROLLER))
+                metrics.extend(controller_metrics)
+
+            # port metrics
+            if resource_metrics.get(constants.ResourceType.PORT):
+                port_metrics = self.client.get_port_metrics(
+                    storage_id,
+                    resource_metrics.get(constants.ResourceType.PORT))
+                metrics.extend(port_metrics)
+
+            # disk metrics
+            if resource_metrics.get(constants.ResourceType.DISK):
+                disk_metrics = self.client.get_disk_metrics(
+                    storage_id,
+                    resource_metrics.get(constants.ResourceType.DISK))
+                metrics.extend(disk_metrics)
+
+        except exception.DelfinException as err:
+            err_msg = "Failed to collect metrics from OceanStor: %s" %\
+                      (six.text_type(err))
+            LOG.error(err_msg)
+            raise err
+
+        except Exception as err:
+            err_msg = "Failed to collect metrics from OceanStor: %s" %\
+                      (six.text_type(err))
+            LOG.error(err_msg)
+            raise exception.InvalidResults(err_msg)
+
+        return metrics
+
+    @staticmethod
+    def get_capabilities(context):
+        """Get capability of supported driver"""
+        return {
+            'is_historic': False,
+            'resource_metrics': {
+                constants.ResourceType.STORAGE_POOL: consts.POOL_CAP,
+                constants.ResourceType.VOLUME: consts.VOLUME_CAP,
+                constants.ResourceType.CONTROLLER: consts.CONTROLLER_CAP,
+                constants.ResourceType.PORT: consts.PORT_CAP,
+                constants.ResourceType.DISK: consts.DISK_CAP
+            }
+        }
diff --git a/delfin/drivers/huawei/oceanstor/rest_client.py b/delfin/drivers/huawei/oceanstor/rest_client.py
index ab7f78e..249f429 100644
--- a/delfin/drivers/huawei/oceanstor/rest_client.py
+++ b/delfin/drivers/huawei/oceanstor/rest_client.py
@@ -22,6 +22,7 @@ import urllib3
 from urllib3.exceptions import InsecureRequestWarning
 from oslo_log import log as logging
 
+from delfin.common import constants
 from delfin import cryptor
 from delfin import exception
 from delfin.drivers.huawei.oceanstor import consts
@@ -31,6 +32,24 @@ from delfin.i18n import _
 LOG = logging.getLogger(__name__)
 
 
+def _get_timestamp_values(metric, value):
+    timestamp = int(metric['CMO_STATISTIC_TIMESTAMP']) * 1000
+    return {timestamp: value}
+
+
+def _get_selection(selection):
+    selected_metrics = []
+    ids = ''
+    for key, value in consts.OCEANSTOR_METRICS.items():
+        if selection.get(key):
+            selected_metrics.append(key)
+            if ids:
+                ids = ids + ',' + value
+            else:
+                ids = value
+    return selected_metrics, ids
+
+
 class RestClient(object):
     """Common class for Huawei OceanStor storage system."""
 
@@ -367,3 +386,213 @@ class RestClient(object):
                                           "GET",
                                           log_filter_flag=True)
         return result_list
+
+    def _get_performance_switch(self):
+        url = "/performance_statistic_switch"
+        result = self.call(url, method='GET', log_filter_flag=True)
+
+        msg = _('Get performance_statistic_switch failed.')
+        self._assert_rest_result(result, msg)
+        self._assert_data_in_result(result, msg)
+        return result['data']
+
+    def _set_performance_switch(self, value):
+        url = "/performance_statistic_switch"
+        data = {"CMO_PERFORMANCE_SWITCH": value}
+        result = self.call(url, data, method='PUT', log_filter_flag=True)
+
+        msg = _('Set performance_statistic_switch failed.')
+        self._assert_rest_result(result, msg)
+        self._assert_data_in_result(result, msg)
+        return result['data']
+
+    def _get_performance_strategy(self):
+        url = "/performance_statistic_strategy"
+        result = self.call(url, method='GET', log_filter_flag=True)
+
+        msg = _('Get performance_statistic_strategy failed.')
+        self._assert_rest_result(result, msg)
+        self._assert_data_in_result(result, msg)
+        return result['data']
+
+    def _set_performance_strategy(self, hist_enable=1, hist_duration=60,
+                                  auto_stop=0, duration=5, max_duration=0):
+        url = "/performance_statistic_strategy"
+        data = {
+            "CMO_STATISTIC_ARCHIVE_SWITCH": hist_enable,
+            "CMO_STATISTIC_ARCHIVE_TIME": hist_duration,
+            "CMO_STATISTIC_AUTO_STOP": auto_stop,
+            "CMO_STATISTIC_INTERVAL": duration,
+            "CMO_STATISTIC_MAX_TIME": max_duration
+        }
+        result = self.call(url, data, method='PUT', log_filter_flag=True)
+
+        msg = _('Set performance_statistic_strategy failed.')
+        self._assert_rest_result(result, msg)
+        self._assert_data_in_result(result, msg)
+        return result['data']
+
+    def _get_metrics(self, resource_type, resource_id, metrics_ids):
+        url = "/performace_statistic/cur_statistic_data"
+        params = "CMO_STATISTIC_UUID={0}:{1}&CMO_STATISTIC_DATA_ID_LIST={2}&"\
+                 "timeConversion=0&"\
+            .format(resource_type, resource_id, metrics_ids)
+        return self.paginated_call(url, None, "GET",
+                                   params=params, log_filter_flag=True)
+
+    def enable_metrics_collection(self):
+        return self._set_performance_switch('1')
+
+    def disable_metrics_collection(self):
+        return self._set_performance_switch('0')
+
+    def configure_metrics_collection(self):
+        self.disable_metrics_collection()
+        self._set_performance_strategy(hist_enable=1, hist_duration=300,
+                                       auto_stop=0, duration=60,
+                                       max_duration=0)
+        self.enable_metrics_collection()
+
+    def get_pool_metrics(self, storage_id, selection):
+        pools = self.get_all_pools()
+        pool_metrics = []
+
+        select_metrics, select_ids = _get_selection(selection)
+        for pool in pools:
+            metrics = self._get_metrics(pool['TYPE'], pool['ID'],
+                                        select_ids)
+            for metric in metrics:
+                data_list = metric['CMO_STATISTIC_DATA_LIST'].split(",")
+                for index, key in enumerate(select_metrics):
+                    data = int(data_list[index])
+                    if key in consts.CONVERT_TO_MILLI_SECOND_LIST:
+                        data = data * 1000
+                    labels = {
+                        'storage_id': storage_id,
+                        'resource_type': 'pool',
+                        'resource_id': pool['ID'],
+                        'resource_name': pool['NAME'],
+                        'type': 'RAW',
+                        'unit': selection[key]['unit']
+                    }
+                    values = _get_timestamp_values(metric, data)
+                    m = constants.metric_struct(name=key, labels=labels,
+                                                values=values)
+                    pool_metrics.append(m)
+        return pool_metrics
+
+    def get_volume_metrics(self, storage_id, selection):
+        volumes = self.get_all_volumes()
+        volume_metrics = []
+
+        select_metrics, select_ids = _get_selection(selection)
+        for volume in volumes:
+            metrics = self._get_metrics(volume['TYPE'], volume['ID'],
+                                        select_ids)
+            for metric in metrics:
+                data_list = metric['CMO_STATISTIC_DATA_LIST'].split(",")
+                for index, key in enumerate(select_metrics):
+                    data = int(data_list[index])
+                    if key in consts.CONVERT_TO_MILLI_SECOND_LIST:
+                        data = data * 1000
+                    labels = {
+                        'storage_id': storage_id,
+                        'resource_type': 'volume',
+                        'resource_id': volume['ID'],
+                        'resource_name': volume['NAME'],
+                        'type': 'RAW',
+                        'unit': selection[key]['unit']
+                    }
+                    values = _get_timestamp_values(metric, data)
+                    m = constants.metric_struct(name=key, labels=labels,
+                                                values=values)
+                    volume_metrics.append(m)
+        return volume_metrics
+
+    def get_controller_metrics(self, storage_id, selection):
+        controllers = self.get_all_controllers()
+        controller_metrics = []
+
+        select_metrics, select_ids = _get_selection(selection)
+        for controller in controllers:
+            metrics = self._get_metrics(controller['TYPE'], controller['ID'],
+                                        select_ids)
+            for metric in metrics:
+                data_list = metric['CMO_STATISTIC_DATA_LIST'].split(",")
+                for index, key in enumerate(select_metrics):
+                    data = int(data_list[index])
+                    if key in consts.CONVERT_TO_MILLI_SECOND_LIST:
+                        data = data * 1000
+                    labels = {
+                        'storage_id': storage_id,
+                        'resource_type': 'controller',
+                        'resource_id': controller['ID'],
+                        'resource_name': controller['NAME'],
+                        'type': 'RAW',
+                        'unit': selection[key]['unit']
+                    }
+                    values = _get_timestamp_values(metric, data)
+                    m = constants.metric_struct(name=key, labels=labels,
+                                                values=values)
+                    controller_metrics.append(m)
+        return controller_metrics
+
+    def get_port_metrics(self, storage_id, selection):
+        ports = self.get_all_ports()
+        port_metrics = []
+
+        select_metrics, select_ids = _get_selection(selection)
+        for port in ports:
+            # ETH_PORT collection not supported
+            if port['TYPE'] == 213:
+                continue
+            metrics = self._get_metrics(port['TYPE'], port['ID'],
+                                        select_ids)
+            for metric in metrics:
+                data_list = metric['CMO_STATISTIC_DATA_LIST'].split(",")
+                for index, key in enumerate(select_metrics):
+                    data = int(data_list[index])
+                    if key in consts.CONVERT_TO_MILLI_SECOND_LIST:
+                        data = data * 1000
+                    labels = {
+                        'storage_id': storage_id,
+                        'resource_type': 'port',
+                        'resource_id': port['ID'],
+                        'resource_name': port['NAME'],
+                        'type': 'RAW',
+                        'unit': selection[key]['unit']
+                    }
+                    values = _get_timestamp_values(metric, data)
+                    m = constants.metric_struct(name=key, labels=labels,
+                                                values=values)
+                    port_metrics.append(m)
+        return port_metrics
+
+    def get_disk_metrics(self, storage_id, selection):
+        disks = self.get_all_disks()
+        disk_metrics = []
+
+        select_metrics, select_ids = _get_selection(selection)
+        for disk in disks:
+            metrics = self._get_metrics(disk['TYPE'], disk['ID'],
+                                        select_ids)
+            for metric in metrics:
+                data_list = metric['CMO_STATISTIC_DATA_LIST'].split(",")
+                for index, key in enumerate(select_metrics):
+                    data = int(data_list[index])
+                    if key in consts.CONVERT_TO_MILLI_SECOND_LIST:
+                        data = data * 1000
+                    labels = {
+                        'storage_id': storage_id,
+                        'resource_type': 'disk',
+                        'resource_id': disk['ID'],
+                        'type': 'RAW',
+                        'unit': selection[key]['unit'],
+                        'resource_name':
+                            disk['MODEL'] + ':' + disk['SERIALNUMBER']
+                    }
+                    values = _get_timestamp_values(metric, data)
+                    m = constants.metric_struct(name=key, labels=labels,
+                                                values=values)
+                    disk_metrics.append(m)
+        return disk_metrics


Test patch:
diff --git a/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py b/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py
index e6b1bef..20eaa04 100644
--- a/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py
+++ b/delfin/tests/unit/drivers/huawei/oceanstor/test_oceanstor.py
@@ -1152,3 +1152,79 @@ class TestOceanStorStorageDriver(TestCase):
                 driver.list_shares(context)
             self.assertIn('The results are invalid',
                           str(exc.exception))
+
+    @mock.patch.object(RestClient, 'get_disk_metrics')
+    @mock.patch.object(RestClient, 'get_port_metrics')
+    @mock.patch.object(RestClient, 'get_controller_metrics')
+    @mock.patch.object(RestClient, 'get_volume_metrics')
+    @mock.patch.object(RestClient, 'get_pool_metrics')
+    @mock.patch.object(RestClient, 'enable_metrics_collection')
+    @mock.patch.object(RestClient, 'disable_metrics_collection')
+    def test_collect_perf_metrics(self, mock_di, mock_en,
+                                  mock_pool, mock_volume, mock_controller,
+                                  mock_port, mock_disk):
+        driver = create_driver()
+
+        ret = [
+            {
+                'data': [{}],
+                'error': {
+                    'code': 0,
+                    'description': '0'
+                }
+            }
+        ]
+        mock_di.return_value = None
+        mock_en.return_value = None
+
+        mock_pool.return_value = [{}]
+        mock_volume.return_value = [{}]
+        mock_controller.return_value = [{}]
+        mock_port.return_value = [{}]
+        mock_disk.return_value = [{}]
+        with mock.patch.object(RestClient,
+                               'do_call', side_effect=ret):
+            storage_id = 123
+            resource_metrics = {
+                'storagePool': {'iops': 'iops description'},
+                'volume': {'iops': 'iops description'},
+                'port': {'iops': 'iops description'},
+                'disk': {'iops': 'iops description'},
+            }
+            start, end = 0, 1
+            driver.collect_perf_metrics(
+                context, storage_id, resource_metrics, start, end)
+            mock_en.assert_called()
+            mock_di.assert_called()
+        mock_pool.assert_called()
+        mock_volume.assert_called()
+        mock_controller.assert_not_called()
+        mock_port.assert_called()
+        mock_disk.assert_called()
+
+        with mock.patch.object(RestClient, 'get_disk_metrics',
+                               side_effect=exception.DelfinException):
+            with self.assertRaises(Exception) as exc:
+                driver.collect_perf_metrics(context, 0,
+                                            {'disk': {'iops': 'iops'}},
+                                            0, 0)
+            self.assertIn('An unknown exception occurred',
+                          str(exc.exception))
+        with mock.patch.object(RestClient, 'get_disk_metrics',
+                               side_effect=TypeError):
+            with self.assertRaises(Exception) as exc:
+                driver.collect_perf_metrics(context, 0,
+                                            {'disk': {'iops': 'iops'}},
+                                            0, 0)
+            self.assertIn('The results are invalid',
+                          str(exc.exception))
+
+    def test_get_capabilities(self):
+        driver = create_driver()
+        cap = driver.get_capabilities(context)
+        self.assertIsNotNone(cap.get('resource_metrics'))
+        self.assertIsNotNone(cap.get('resource_metrics').get('storagePool'))
+        self.assertIsNotNone(cap.get('resource_metrics').get('volume'))
+        self.assertIsNotNone(cap.get('resource_metrics').get('controller'))
+        self.assertIsNotNone(cap.get('resource_metrics').get('port'))
+        self.assertIsNotNone(cap.get('resource_metrics').get('disk'))
diff --git a/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py b/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py
index 9682b55..f255f58 100644
--- a/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py
+++ b/delfin/tests/unit/drivers/huawei/oceanstor/test_rest_client.py
@@ -155,3 +155,215 @@ class TestOceanStorRestClient(TestCase):
         self.assertEqual(data['data']['data'], 'dummy')
         mock_call.assert_called_with("/lun", None, 'GET',
                                      log_filter_flag=True)
+
+    @mock.patch.object(RestClient, 'call')
+    @mock.patch.object(RestClient, 'login')
+    def test_enable_metrics_collection(self, mock_login, mock_call):
+        mock_login.return_value = None
+        mock_call.return_value = RESP
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        data = rest_client.enable_metrics_collection()
+        self.assertEqual(data['data'], 'dummy')
+        mock_call.assert_called_with("/performance_statistic_switch",
+                                     {'CMO_PERFORMANCE_SWITCH': '1'},
+                                     log_filter_flag=True, method='PUT')
+
+    @mock.patch.object(RestClient, 'call')
+    @mock.patch.object(RestClient, 'login')
+    def test_disable_metrics_collection(self, mock_login, mock_call):
+        mock_login.return_value = None
+        mock_call.return_value = RESP
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        data = rest_client.disable_metrics_collection()
+        self.assertEqual(data['data'], 'dummy')
+        mock_call.assert_called_with("/performance_statistic_switch",
+                                     {'CMO_PERFORMANCE_SWITCH': '0'},
+                                     log_filter_flag=True, method='PUT')
+
+    @mock.patch.object(RestClient, 'disable_metrics_collection')
+    @mock.patch.object(RestClient, 'enable_metrics_collection')
+    @mock.patch.object(RestClient, 'call')
+    @mock.patch.object(RestClient, 'login')
+    def test_configure_metrics_collection(self, mock_login, mock_call,
+                                          mock_en, mock_di):
+        mock_login.return_value = None
+        mock_call.return_value = RESP
+        mock_en.return_value = None
+        mock_di.return_value = None
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        rest_client.configure_metrics_collection()
+        data = {
+            "CMO_STATISTIC_ARCHIVE_SWITCH": 1,
+            "CMO_STATISTIC_ARCHIVE_TIME": 300,
+            "CMO_STATISTIC_AUTO_STOP": 0,
+            "CMO_STATISTIC_INTERVAL": 60,
+            "CMO_STATISTIC_MAX_TIME": 0
+        }
+        mock_call.assert_called_with("/performance_statistic_strategy",
+                                     data,
+                                     log_filter_flag=True, method='PUT')
+
+    @mock.patch.object(RestClient, 'get_all_pools')
+    @mock.patch.object(RestClient, 'paginated_call')
+    @mock.patch.object(RestClient, 'login')
+    def test_get_pool_metrics(self, mock_login, mock_call,
+                              mock_pools):
+        mock_login.return_value = None
+        mock_call.return_value = [{'CMO_STATISTIC_DATA_LIST': '12,25',
+                                   'CMO_STATISTIC_TIMESTAMP': 0}]
+        mock_pools.return_value = [
+            {'ID': '123', 'TYPE': '100', 'NAME': 'pool'}
+        ]
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        metrics = rest_client.get_pool_metrics('', {'iops': {'unit': 'IOPS'}})
+        mock_call.assert_called_with(
+            "/performace_statistic/cur_statistic_data",
+            None, 'GET', log_filter_flag=True,
+            params='CMO_STATISTIC_UUID=100:123&CMO_STATISTIC_DATA_ID_LIST=22&'
+                   'timeConversion=0&'
+        )
+        expected_label = {
+            'storage_id': '',
+            'resource_type': 'pool',
+            'resource_id': '123',
+            'type': 'RAW',
+            'unit': 'IOPS',
+            'resource_name': 'pool'
+        }
+        self.assertEqual(metrics[0].name, 'iops')
+        self.assertDictEqual(metrics[0].labels, expected_label)
+        self.assertListEqual(list(metrics[0].values.values()), [12])
+
+    @mock.patch.object(RestClient, 'get_all_volumes')
+    @mock.patch.object(RestClient, 'paginated_call')
+    @mock.patch.object(RestClient, 'login')
+    def test_get_volume_metrics(self, mock_login, mock_call,
+                                mock_volumes):
+        mock_login.return_value = None
+        mock_call.return_value = [{'CMO_STATISTIC_DATA_LIST': '12,25',
+                                   'CMO_STATISTIC_TIMESTAMP': 0}]
+        mock_volumes.return_value = [
+            {'ID': '123', 'TYPE': '100', 'NAME': 'volume'}
+        ]
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        metrics = rest_client.get_volume_metrics(
+            '', {'iops': {'unit': 'IOPS'}})
+        mock_call.assert_called_with(
+            "/performace_statistic/cur_statistic_data",
+            None, 'GET', log_filter_flag=True,
+            params='CMO_STATISTIC_UUID=100:123&CMO_STATISTIC_DATA_ID_LIST=22&'
+                   'timeConversion=0&'
+        )
+        expected_label = {
+            'storage_id': '',
+            'resource_type': 'volume',
+            'resource_id': '123',
+            'type': 'RAW',
+            'unit': 'IOPS',
+            'resource_name': 'volume'
+        }
+        self.assertEqual(metrics[0].name, 'iops')
+        self.assertDictEqual(metrics[0].labels, expected_label)
+        self.assertListEqual(list(metrics[0].values.values()), [12])
+
+    @mock.patch.object(RestClient, 'get_all_controllers')
+    @mock.patch.object(RestClient, 'paginated_call')
+    @mock.patch.object(RestClient, 'login')
+    def test_get_controller_metrics(self, mock_login, mock_call,
+                                    mock_controllers):
+        mock_login.return_value = None
+        mock_call.return_value = [{'CMO_STATISTIC_DATA_LIST': '12,25',
+                                   'CMO_STATISTIC_TIMESTAMP': 0}]
+        mock_controllers.return_value = [
+            {'ID': '123', 'TYPE': '100', 'NAME': 'controller'}
+        ]
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        metrics = rest_client.get_controller_metrics(
+            '', {'iops': {'unit': 'IOPS'}})
+        mock_call.assert_called_with(
+            "/performace_statistic/cur_statistic_data",
+            None, 'GET', log_filter_flag=True,
+            params='CMO_STATISTIC_UUID=100:123&CMO_STATISTIC_DATA_ID_LIST=22&'
+                   'timeConversion=0&'
+        )
+        expected_label = {
+            'storage_id': '',
+            'resource_type': 'controller',
+            'resource_id': '123',
+            'type': 'RAW',
+            'unit': 'IOPS',
+            'resource_name': 'controller'
+        }
+        self.assertEqual(metrics[0].name, 'iops')
+        self.assertDictEqual(metrics[0].labels, expected_label)
+        self.assertListEqual(list(metrics[0].values.values()), [12])
+
+    @mock.patch.object(RestClient, 'get_all_ports')
+    @mock.patch.object(RestClient, 'paginated_call')
+    @mock.patch.object(RestClient, 'login')
+    def test_get_port_metrics(self, mock_login, mock_call,
+                              mock_ports):
+        mock_login.return_value = None
+        mock_call.return_value = [{'CMO_STATISTIC_DATA_LIST': '12,25',
+                                   'CMO_STATISTIC_TIMESTAMP': 0}]
+        mock_ports.return_value = [
+            {'ID': '123', 'TYPE': '100', 'NAME': 'port'}
+        ]
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        metrics = rest_client.get_port_metrics('', {'iops': {'unit': 'IOPS'}})
+        mock_call.assert_called_with(
+            "/performace_statistic/cur_statistic_data",
+            None, 'GET', log_filter_flag=True,
+            params='CMO_STATISTIC_UUID=100:123&CMO_STATISTIC_DATA_ID_LIST=22&'
+                   'timeConversion=0&'
+        )
+        expected_label = {
+            'storage_id': '',
+            'resource_type': 'port',
+            'resource_id': '123',
+            'type': 'RAW',
+            'unit': 'IOPS',
+            'resource_name': 'port'
+        }
+        self.assertEqual(metrics[0].name, 'iops')
+        self.assertDictEqual(metrics[0].labels, expected_label)
+        self.assertListEqual(list(metrics[0].values.values()), [12])
+
+    @mock.patch.object(RestClient, 'get_all_disks')
+    @mock.patch.object(RestClient, 'paginated_call')
+    @mock.patch.object(RestClient, 'login')
+    def test_get_disk_metrics(self, mock_login, mock_call,
+                              mock_disks):
+        mock_login.return_value = None
+        mock_call.return_value = [{'CMO_STATISTIC_DATA_LIST': '12,25',
+                                   'CMO_STATISTIC_TIMESTAMP': 0}]
+        mock_disks.return_value = [
+            {'ID': '123', 'TYPE': '100', 'MODEL': 'disk', 'SERIALNUMBER': '0'}
+        ]
+        kwargs = ACCESS_INFO
+        rest_client = RestClient(**kwargs)
+        metrics = rest_client.get_disk_metrics('', {'iops': {'unit': 'IOPS'}})
+        mock_call.assert_called_with(
+            "/performace_statistic/cur_statistic_data",
+            None, 'GET', log_filter_flag=True,
+            params='CMO_STATISTIC_UUID=100:123&CMO_STATISTIC_DATA_ID_LIST=22&'
+                   'timeConversion=0&'
+        )
+        expected_label = {
+            'storage_id': '',
+            'resource_type': 'disk',
+            'resource_id': '123',
+            'resource_name': 'disk:0',
+            'type': 'RAW',
+            'unit': 'IOPS',
+        }
+        self.assertEqual(metrics[0].name, 'iops')
+        self.assertDictEqual(metrics[0].labels, expected_label)
+        self.assertListEqual(list(metrics[0].values.values()), [12])