cps-ncmp-rest-stub/dependency-reduced-pom.xml
cps-application/archunit_store
cps-ri/src/main/resources/changelog/db/changes/data/dmi/generated-csv/generated_yang_resource_*
+checkstyle/src/main/__pycache__/
+docs/venv/
+docs/__pycache__/
target/
log/
<!--
============LICENSE_START=======================================================
Copyright (C) 2020 Pantheon.tech
- Modifications Copyright (C) 2023 Nordix Foundation
+ Modifications Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved.
================================================================================
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
</profile>
</profiles>
- <properties>
- <onap.nexus.url>https://nexus.onap.org</onap.nexus.url>
- <releaseNexusPath>/content/repositories/releases/</releaseNexusPath>
- <sonar.skip>true</sonar.skip>
- <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath>
- </properties>
-
<build>
<pluginManagement>
<plugins>
</plugin>
</plugins>
</build>
-
- <distributionManagement>
- <repository>
- <id>ecomp-releases</id>
- <name>ECOMP Release Repository</name>
- <url>${onap.nexus.url}${releaseNexusPath}</url>
- </repository>
- <snapshotRepository>
- <id>ecomp-snapshots</id>
- <name>ECOMP Snapshot Repository</name>
- <url>${onap.nexus.url}${snapshotNexusPath}</url>
- </snapshotRepository>
- </distributionManagement>
</project>
\ No newline at end of file
*/
@Override
@SuppressWarnings("deprecation") // mapOldConditionProperties method will be removed in Release 12
- @CountCmHandleSearchExecution(methodName = "searchCmHandles", interfaceName = "CPS-E-05")
+ @CountCmHandleSearchExecution(methodName = "searchCmHandles", interfaceName = "CPS-E-05",
+ description = "Search for cm handles within CPS-E-05 interface")
public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles(
final CmHandleQueryParameters cmHandleQueryParameters) {
final CmHandleQueryApiParameters cmHandleQueryApiParameters =
* @return collection of cm handle ids
*/
@Override
- @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-E-05")
+ @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-E-05",
+ description = "Search for cm handles within CPS-E-05 interface")
public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters,
final Boolean outputAlternateId) {
final CmHandleQueryApiParameters cmHandleQueryApiParameters =
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021-2022 Bell Canada
- * Modifications Copyright (C) 2022-2025 Nordix Foundation
+ * Modifications Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* @return list of cm handle IDs
*/
@Override
- @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-NCMP-I-01")
+ @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-NCMP-I-01",
+ description = "Search for cm handle ids within CPS-NCMP-I-01 interface")
public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters,
final Boolean outputAlternateId) {
final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = ncmpRestInputMapper
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2025 Nordix Foundation
+ * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* @return the CPS and NCMP interface name
*/
String interfaceName();
+
+ /**
+ * Capture the description to facilitate metric scraping.
+ *
+ * @return the description of the metric.
+ */
+ String description();
}
* @return cm handle state gauge
*/
@Bean
+ @TimedCustom(name = "cps_ncmp_inventory_cm_handles_by_state{state=ADVISED}",
+ description = "Current number of cm handles in advised state")
public Gauge advisedCmHandles(final MeterRegistry meterRegistry) {
return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
value -> cmHandlesByState.get("advisedCmHandlesCount"))
* @return cm handle state gauge
*/
@Bean
+ @TimedCustom(name = "cps_ncmp_inventory_cm_handles_by_state{state=READY}",
+ description = "Current number of cm handles in ready state")
public Gauge readyCmHandles(final MeterRegistry meterRegistry) {
return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
value -> cmHandlesByState.get("readyCmHandlesCount"))
* @return cm handle state gauge
*/
@Bean
+ @TimedCustom(name = "cps_ncmp_inventory_cm_handles_by_state{state=LOCKED}",
+ description = "Current number of cm handles in locked state")
public Gauge lockedCmHandles(final MeterRegistry meterRegistry) {
return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
value -> cmHandlesByState.get("lockedCmHandlesCount"))
* @return cm handle state gauge
*/
@Bean
+ @TimedCustom(name = "cps_ncmp_inventory_cm_handles_by_state{state=DELETING}",
+ description = "Current number of cm handles in deleting state")
public Gauge deletingCmHandles(final MeterRegistry meterRegistry) {
return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
value -> cmHandlesByState.get("deletingCmHandlesCount"))
* @return cm handle state gauge
*/
@Bean
+ @TimedCustom(name = "cps_ncmp_inventory_cm_handles_by_state{state=DELETED}",
+ description = "Number of cm handles that have been deleted since the application started")
public Gauge deletedCmHandles(final MeterRegistry meterRegistry) {
return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
value -> cmHandlesByState.get("deletedCmHandlesCount"))
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.config;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Custom annotation to enable metric scraping.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TimedCustom {
+ /**
+ * Stores the name for a metric.
+ *
+ * @return the name of the metric.
+ */
+ String name();
+
+ /**
+ * Stores the description for a metric.
+ *
+ * @return the description of the metric.
+ */
+ String description();
+}
--- /dev/null
+# ============LICENSE_START=======================================================
+# Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=========================================================
+
+import os
+import re
+
+def find_java_files(root_dir):
+ """
+ Recursively finds all .java files within the given root directory.
+
+ Args:
+ root_dir (str): The root directory to search.
+
+ Returns:
+ list: A list of absolute paths to .java files.
+ """
+ java_files = []
+ for root, _, files in os.walk(root_dir):
+ for file in files:
+ if file.endswith(".java"):
+ java_files.append(os.path.join(root, file))
+ return java_files
+
+def scrape_metrics(file_content):
+ """
+ Matches @CountCmHandleSearchExecution, @Timed, and @TimedCustom and
+ Extracts name (confusingly labeled as 'value' in @Timed) and description from the given Java file content.
+ The regex will also handle the new line if the annotation would not fit in a single line.
+
+ Args:
+ file_content (str): The content of a Java file.
+
+ Returns:
+ list: A list of formatted metric strings.
+ """
+ pattern_regex = re.compile(r'@(CountCmHandleSearchExecution|Timed|TimedCustom)\((?:name\s*=\s*"(.*?)",?|value\s*=\s*"(.*?)",?)?.*?description\s*=\s*"(.*?)"', re.DOTALL)
+ all_metrics = []
+ matches = pattern_regex.findall(file_content)
+ for match in matches:
+ count_metric = match[0]
+ if count_metric == "CountCmHandleSearchExecution":
+ name = "cm_handle_search_invocation_total"
+ else:
+ name = match[1]
+ value = match[2]
+ description = match[3]
+ all_metrics.append(f'"{name or value}","{description}"')
+ return all_metrics
+
+def scrape_all_metrics_from_file(file_path):
+ """
+ Scrapes all defined metrics from a single Java file.
+
+ Args:
+ file_path (str): The path to the Java file.
+
+ Returns:
+ list: A list of all extracted metric strings from the file.
+ """
+ all_metrics = []
+ with open(file_path, 'r') as f:
+ java_class_content = f.read()
+ all_metrics.extend(scrape_metrics(java_class_content))
+ return all_metrics
+
+def write_metrics_to_file(metrics_data, output_file):
+ """
+ Writes the extracted metrics data to the specified output file.
+
+ Args:
+ metrics_data (list): A list of metric strings to write.
+ output_file (str): The path to the output file.
+ """
+ if metrics_data:
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
+ with open(output_file, 'w') as outfile:
+ outfile.write('"Metric Name","Description"\n')
+ for metric in metrics_data:
+ outfile.write(metric + '\n')
+ print(f"{len(metrics_data)} scraped metrics written to: {output_file}")
+
+def search_metrics_and_scrape(root_dir, output_file):
+ """
+ Orchestrates the search and scraping of metrics from Java files.
+
+ Args:
+ root_dir (str): The root directory to search for .java files.
+ output_file (str): The text file to store the metrics.
+ """
+ java_files = find_java_files(root_dir)
+ all_scraped_metrics = []
+ for java_file in java_files:
+ metrics = scrape_all_metrics_from_file(java_file)
+ all_scraped_metrics.extend(metrics)
+ write_metrics_to_file(all_scraped_metrics, output_file)
+
+if __name__ == "__main__":
+ # Get the absolute path of the current directory.
+ current_directory = os.path.dirname(os.path.abspath(__file__))
+
+ # Get the absolute path of the cps root directory.
+ cps_root_directory = os.path.abspath(os.path.join(current_directory, ".."))
+
+ # Define the location for the output file, and ensure its directory exists.
+ output_file = os.path.join(current_directory, "csv", "metrics.csv")
+
+ # Search and scrape the metrics.
+ search_metrics_and_scrape(cps_root_directory, output_file)
\ No newline at end of file
.. This work is licensed under a Creative Commons Attribution 4.0 International License.
.. http://creativecommons.org/licenses/by/4.0
-.. Copyright (C) 2021-2025 Nordix Foundation
+.. Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
.. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
.. _adminGuide:
Metrics
-------
+Below table lists all CPS-NCMP custom metrics
+
+.. csv-table::
+ :file: csv/metrics.csv
+ :widths: 50, 50
+
Prometheus Metrics can be checked at the following endpoint
.. code::
--- /dev/null
+"Metric Name","Description"
+"cps.ncmp.controller.get","Time taken to get resource data from datastore"
+"cm_handle_search_invocation_total","Search for cm handles within CPS-E-05 interface"
+"cm_handle_search_invocation_total","Search for cm handles within CPS-E-05 interface"
+"cm_handle_search_invocation_total","Search for cm handle ids within CPS-NCMP-I-01 interface"
+"cps.ncmp.inventory.controller.update","Time taken to handle registration request"
+"cps_ncmp_inventory_cm_handles_by_state{state=ADVISED}","Current number of cm handles in advised state"
+"cps_ncmp_inventory_cm_handles_by_state{state=READY}","Current number of cm handles in ready state"
+"cps_ncmp_inventory_cm_handles_by_state{state=LOCKED}","Current number of cm handles in locked state"
+"cps_ncmp_inventory_cm_handles_by_state{state=DELETING}","Current number of cm handles in deleting state"
+"cps_ncmp_inventory_cm_handles_by_state{state=DELETED}","Number of cm handles that have been deleted since the application started"
+"cps.ncmp.dmi.get","Time taken to fetch the resource data from operational data store for given cm handle "
+"cps.ncmp.inventory.persistence.datanode.get","Time taken to get a data node (from ncmp dmi registry)"
+"cps.ncmp.inventory.persistence.datanode.get","Time taken to get a data node (from ncmp dmi registry)"
+"cps.ncmp.inventory.module.references.from.dmi","Time taken to get all module references for a cm handle from dmi"
+"cps.ncmp.inventory.yang.resources.from.dmi","Time taken to get list of yang resources from dmi"
+"cps.ncmp.cmhandle.state.update.batch","Time taken to update a batch of cm handle states"
+"cps.rest.admin.controller.schemaset.create","Time taken to create schemaset from controller"
+"cps.data.controller.datanode.query.v1","Time taken to query data nodes"
+"cps.data.controller.datanode.query.v2","Time taken to query data nodes"
+"cps.data.controller.datanode.query.across.anchors","Time taken to query data nodes across anchors"
+"cps.data.controller.datanode.get.v1","Time taken to get data node"
+"cps.data.controller.datanode.get.v2","Time taken to get data node"
+"cps.delta.controller.get.delta","Time taken to get delta between anchors"
+"cps.delta.controller.get.delta","Time taken to get delta between anchors"
+"cps.module.persistence.schemaset.create","Time taken to store a schemaset (list of module references)"
+"cps.module.persistence.schemaset.createFromNewAndExistingModules","Time taken to store a schemaset (from new and existing)"
+"cps.data.persistence.service.datanode.query","Time taken to query data nodes"
+"cps.data.persistence.service.datanode.query.anchors","Time taken to query data nodes across all anchors or list of anchors"
+"cps.data.persistence.service.datanode.get","Time taken to get a data node"
+"cps.data.persistence.service.datanode.batch.get","Time taken to get data nodes"
+"cps.dataupdate.events.send","Time taken to send Data Update event"
+"cps.module.service.schemaset.create","Time taken to create (and store) a schemaset"
+"cps.data.service.datanode.query","Time taken to query data nodes"
+"cps.data.service.datanode.query","Time taken to query data nodes with a limit on results"
+"cps.data.service.datanode.root.save","Time taken to save a root data node"
+"cps.data.service.datanode.child.save","Time taken to save a child data node"
+"cps.data.service.list.element.save","Time taken to save list elements"
+"cps.data.service.datanode.get","Time taken to get data nodes for an xpath"
+"cps.data.service.datanode.batch.get","Time taken to get a batch of data nodes"
+"cps.data.service.datanode.leaves.update","Time taken to update a batch of leaf data nodes"
+"cps.data.service.datanode.leaves.descendants.leaves.update","Time taken to update data node leaves and existing descendants leaves"
+"cps.data.service.datanode.descendants.update","Time taken to update a data node and descendants"
+"cps.data.service.datanode.descendants.batch.update","Time taken to update a batch of data nodes and descendants"
+"cps.data.service.list.update","Time taken to update a list"
+"cps.data.service.list.batch.update","Time taken to update a batch of lists"
+"cps.data.service.datanode.delete","Time taken to delete a datanode"
+"cps.data.service.datanode.batch.delete","Time taken to delete a batch of datanodes"
+"cps.data.service.datanode.delete.anchor","Time taken to delete all datanodes for an anchor"
+"cps.data.service.datanode.delete.anchor.batch","Time taken to delete all datanodes for multiple anchors"
+"cps.data.service.list.delete","Time taken to delete a list or list element"
+"cps.delta.service.get.delta","Time taken to get delta between anchors"
+"cps.delta.service.get.delta","Time taken to get delta between anchor and a payload"
+"cps.utils.yangparser.nodedata.with.parent.parse","Time taken to parse node data with a parent"
+"cps.utils.yangparser.nodedata.with.parent.with.yangResourceMap.parse","Time taken to parse node data with a parent"
+"cps.yangtextschemasourceset.build","Time taken to build a yang text schema source set"
+"cps.yang.schemasourceset.build","Time taken to build a ODL yang Model"
--- /dev/null
+# ============LICENSE_START=======================================================
+# Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=========================================================
+
+import unittest
+import os
+import tempfile
+import time
+from ScrapeMetrics import (
+ scrape_all_metrics_from_file
+)
+
+class TestScrapeMetrics(unittest.TestCase):
+
+ def setUp(self):
+ """Set up temporary directory and files for testing."""
+ self.temp_dir = tempfile.TemporaryDirectory()
+ self.test_root = self.temp_dir.name
+
+ def tearDown(self):
+ """Clean up temporary directory and files."""
+ self.temp_dir.cleanup()
+
+ def _create_java_file(self, relative_path, content):
+ """Helper function to create a test .java file."""
+ file_path = os.path.join(self.test_root, relative_path)
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
+ with open(file_path, 'w') as f:
+ f.write(content)
+ return file_path
+
+ def test_scrape_metrics_from_file(self):
+ """Test scraping all metrics from a single Java file."""
+ file_content = """
+ package com.example;
+
+ @CountCmHandleSearchExecution(
+ description = "A description does not fit the a single line")
+ public void myMethod() {}
+
+ @Timed(value="timed", description="A timed metric")
+ public void anotherMethod() {}
+
+ @TimedCustom(name="cps_ncmp_inventory_cm_handles_by_state{state=DELETING}", description="A custom timed metric")
+ public void anotherMethod() {}
+
+ @NotTimed
+ public void notTimedMethod() {}
+ """
+ test_file = self._create_java_file("com/example/MyService.java", file_content)
+ expected_metrics = [
+ '"cm_handle_search_invocation_total","A description does not fit the a single line"',
+ '"timed","A timed metric"',
+ '"cps_ncmp_inventory_cm_handles_by_state{state=DELETING}","A custom timed metric"'
+ ]
+ result = scrape_all_metrics_from_file(test_file)
+ self.assertEqual(len(result), 3)
+ self.assertEqual(result, expected_metrics)
+
+ def test_verify_metrics_file(self):
+ """Test if metrics.csv was modified less than 1 minute ago and has 56 lines."""
+
+ # Get the absolute path of the current directory.
+ current_directory = os.path.dirname(os.path.abspath(__file__))
+
+ metrics_file = os.path.join(current_directory, "csv/metrics.csv")
+
+ # Check if the file exists
+ self.assertTrue(os.path.exists(metrics_file), "metrics.csv does not exist.")
+
+ # Check modification time
+ modification_time_in_seconds = os.path.getmtime(metrics_file)
+ time_difference_in_seconds = time.time() - modification_time_in_seconds
+ self.assertLess(time_difference_in_seconds, 60, "metrics.csv was not modified in the last minute.")
+
+ # Check number of lines
+ with open(metrics_file, 'r') as f:
+ lines = f.readlines()
+
+ expected_number_of_metrics = 56
+ expected_number_of_lines = expected_number_of_metrics + 1 # Header
+ self.assertEqual(len(lines), expected_number_of_lines, f"metrics.csv does not have {expected_number_of_lines} lines.")
+
+if __name__ == '__main__':
+ # Ensure the script's directory is in the Python path for importing
+ import sys
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ if script_dir not in sys.path:
+ sys.path.insert(0, script_dir)
+ unittest.main()
\ No newline at end of file