diff options
author | 2025-03-13 09:58:17 +0000 | |
---|---|---|
committer | 2025-03-13 09:58:17 +0000 | |
commit | 00e49d680dec96a6aebcbb01b17317b6615232b2 (patch) | |
tree | a5014043d111a1b9369ee6c313654dcea8d57b98 | |
parent | 36027000ecc2d0bbffc7cd45fe3941db77e1d178 (diff) | |
parent | 6957d0093eb370e0bf0be7dbb610430ea30c70a6 (diff) |
Merge "Add prometheus metrics for searches and id-searches"
6 files changed, 266 insertions, 3 deletions
diff --git a/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java index c1d65758c7..28ff7c307c 100644 --- a/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java +++ b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2024 Nordix Foundation + * Copyright (C) 2024-2025 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ public class ArchitectureTestBase { "java..", "lombok..", "org.apache..", + "org.aspectj..", "org.mapstruct..", "org.opendaylight..", "org.slf4j..", diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index 317f6b70e1..d7b38d1a46 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021-2025 Nordix Foundation * Modifications Copyright (C) 2021 highstreet technologies GmbH * Modifications Copyright (C) 2021-2022 Bell Canada * ================================================================================ @@ -57,6 +57,7 @@ import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState; import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties; import org.onap.cps.ncmp.rest.util.CmHandleStateMapper; +import org.onap.cps.ncmp.rest.util.CountCmHandleSearchExecution; import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper; import org.onap.cps.ncmp.rest.util.DeprecationHelper; import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper; @@ -256,6 +257,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { */ @Override @SuppressWarnings("deprecation") // mapOldConditionProperties method will be removed in Release 12 + @CountCmHandleSearchExecution(methodName = "searchCmHandles", interfaceName = "CPS-E-05") public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles( final CmHandleQueryParameters cmHandleQueryParameters) { final CmHandleQueryApiParameters cmHandleQueryApiParameters = @@ -276,6 +278,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { * @return collection of cm handle ids */ @Override + @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-E-05") public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters, final Boolean outputAlternateId) { final CmHandleQueryApiParameters cmHandleQueryApiParameters = diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java index 0e27ba9355..e412107753 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2022-2024 Nordix Foundation + * Modifications Copyright (C) 2022-2025 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; import org.onap.cps.ncmp.rest.model.CmHandlerRegistrationErrorResponse; import org.onap.cps.ncmp.rest.model.DmiPluginRegistrationErrorResponse; import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration; +import org.onap.cps.ncmp.rest.util.CountCmHandleSearchExecution; import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -60,6 +61,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor * @return list of cm handle IDs */ @Override + @CountCmHandleSearchExecution(methodName = "searchCmHandleIds", interfaceName = "CPS-NCMP-I-01") public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters, final Boolean outputAlternateId) { final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = ncmpRestInputMapper diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounter.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounter.java new file mode 100644 index 0000000000..ecd248d89f --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounter.java @@ -0,0 +1,84 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 Nordix Foundation + * ================================================================================ + * 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.rest.util; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; +import org.onap.cps.ncmp.rest.model.ConditionProperties; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@RequiredArgsConstructor +@Slf4j +public class CmHandleSearchExecutionCounter { + + private static final String NO_CONDITION = "NONE"; + + private final MeterRegistry meterRegistry; + + /** + * Counts the number of invocations of the methods annotated with @CountCmHandleSearchExecution based on the search + * conditions dynamically added. If search is executed without condition then it would be tagged as NONE, otherwise + * the conditions are concatenated with _ as separator. + * + * @param joinPoint join point + * @param countCmHandleSearchExecution count the cm handle search conditions + */ + @Before("@annotation(countCmHandleSearchExecution)") + public void cmHandleSearchExecutionCounter(final JoinPoint joinPoint, + final CountCmHandleSearchExecution countCmHandleSearchExecution) { + final Object[] args = joinPoint.getArgs(); + + if (args.length == 0 || !(args[0] instanceof CmHandleQueryParameters cmHandleQueryParameters)) { + log.warn("Method {} is missing required CmHandleQueryParameters argument", joinPoint.getSignature()); + return; + } + + final String conditionTag = Optional.ofNullable(cmHandleQueryParameters.getCmHandleQueryParameters()) + .filter(conditionTypes -> !conditionTypes.isEmpty()) + .map(CmHandleSearchExecutionCounter::conditionTag) + .orElse(NO_CONDITION); + + Counter.builder("cm_handle_search_invocations") + .tag("method", countCmHandleSearchExecution.methodName()) + .tag("cps-interface", countCmHandleSearchExecution.interfaceName()) + .tag("conditions", conditionTag) + .description("Number of invocations of search methods based on condition types") + .register(meterRegistry) + .increment(); + } + + private static String conditionTag(final List<@Valid ConditionProperties> conditionTypes) { + return conditionTypes.stream().map(ConditionProperties::getConditionName).sorted() + .collect(Collectors.joining("_")); + } +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CountCmHandleSearchExecution.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CountCmHandleSearchExecution.java new file mode 100644 index 0000000000..27a0c4a87a --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/CountCmHandleSearchExecution.java @@ -0,0 +1,45 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 Nordix Foundation + * ================================================================================ + * 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.rest.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CountCmHandleSearchExecution { + + /** + * Capture the method name for which the number of invocations needs to be tracked. + * + * @return the search method name + */ + String methodName(); + + /** + * Capture the CPS and NCMP interface name of the called method. + * + * @return the CPS and NCMP interface name + */ + String interfaceName(); +} diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounterSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounterSpec.groovy new file mode 100644 index 0000000000..bdadfc8689 --- /dev/null +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/CmHandleSearchExecutionCounterSpec.groovy @@ -0,0 +1,128 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 Nordix Foundation + * ================================================================================ + * 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.rest.util + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry +import org.aspectj.lang.JoinPoint +import org.aspectj.lang.Signature +import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters +import org.onap.cps.ncmp.rest.model.ConditionProperties +import spock.lang.Specification + +class CmHandleSearchExecutionCounterSpec extends Specification { + + def meterRegistry = new SimpleMeterRegistry() + def mockJoinPoint = Mock(JoinPoint) + def mockCountCmHandleSearchExecutionAnnotation = Mock(CountCmHandleSearchExecution) + def mockSignature = Mock(Signature) + + def objectUnderTest = new CmHandleSearchExecutionCounter(meterRegistry) + + def setup() { + mockCountCmHandleSearchExecutionAnnotation.methodName() >> 'testMethod' + mockCountCmHandleSearchExecutionAnnotation.interfaceName() >> 'testInterface' + mockSignature.toString() >> 'testSignature' + mockJoinPoint.getSignature() >> mockSignature + } + + def 'should track search with conditions'() { + given: 'CmHandleQueryParameters with conditions' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def condition1 = new ConditionProperties(conditionName: 'condition1') + def condition2 = new ConditionProperties(conditionName: 'condition2') + cmHandleQueryParameters.addCmHandleQueryParametersItem(condition1).addCmHandleQueryParametersItem(condition2) + and: 'joinPoint returns the parameters' + mockJoinPoint.getArgs() >> [cmHandleQueryParameters] + when: 'the annotated method is called' + objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation) + then: 'the counter should be registered' + def counter = findCounter('cm_handle_search_invocations', [ + 'method' : 'testMethod', + 'cps-interface': 'testInterface', + 'conditions' : 'condition1_condition2' + ]) + and: 'is incremented once' + assert counter.count() == 1 + } + + def 'should track search with no conditions as NONE'() { + given: 'empty CmHandleQueryParameters' + def cmHandleQueryParameters = new CmHandleQueryParameters() + and: 'joinPoint returns the parameters' + mockJoinPoint.getArgs() >> [cmHandleQueryParameters] + when: 'the annotated method is called' + objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation) + then: 'the counter should be registered with NONE tag' + def counter = findCounter('cm_handle_search_invocations', [ + method : 'testMethod', + 'cps-interface': 'testInterface', + conditions : 'NONE' + ]) + and: 'is incremented once' + assert counter.count() == 1 + } + + def 'should not create counter when args are empty'() { + given: 'joinPoint with empty args' + mockJoinPoint.getArgs() >> [] + when: 'the aspect method is called' + objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation) + then: 'no counter should be registered' + assert meterRegistry.find('cm_handle_search_invocations').counters().isEmpty() + } + + def 'should not create counter when first arg is not CmHandleQueryParameters'() { + given: 'joinPoint with non-CmHandleQueryParameters arg' + mockJoinPoint.getArgs() >> ['not a CmHandleQueryParameters'] + when: 'the aspect method is called' + objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation) + then: 'no counter should be registered' + assert meterRegistry.find('cm_handle_search_invocations').counters().isEmpty() + } + + def 'should sort condition names alphabetically'() { + given: 'CmHandleQueryParameters with unsorted conditions' + def cmHandleQueryParameters = new CmHandleQueryParameters() + def condition1 = new ConditionProperties(conditionName: 'zCondition') + def condition2 = new ConditionProperties(conditionName: 'aCondition') + cmHandleQueryParameters.addCmHandleQueryParametersItem(condition1).addCmHandleQueryParametersItem(condition2) + and: 'joinPoint returns our parameters' + mockJoinPoint.getArgs() >> [cmHandleQueryParameters] + when: 'the aspect method is called' + objectUnderTest.cmHandleSearchExecutionCounter(mockJoinPoint, mockCountCmHandleSearchExecutionAnnotation) + then: 'the counter should be registered with alphabetically sorted tags' + def counter = findCounter('cm_handle_search_invocations', [ + 'method' : 'testMethod', + 'cps-interface': 'testInterface', + 'conditions' : 'aCondition_zCondition' + ]) + and: 'counter is incremented once' + assert counter.count() == 1 + } + + def findCounter(name, tags) { + def counterSearch = meterRegistry.find(name) + tags.each { key, value -> + counterSearch = counterSearch.tag(key, value) + } + return counterSearch.counter() + } +}
\ No newline at end of file |