diff --git a/report_coverage_context.sh b/report_coverage_context.sh index 3a404a62698cdd95f94c9ed7d4c8b4b073778d08..95966ead0bdf84b39be3e3f3063e1b93dfad32f1 100755 --- a/report_coverage_context.sh +++ b/report_coverage_context.sh @@ -1,3 +1,3 @@ #!/bin/bash -./report_coverage_all.sh | grep --color -E -i "^context/.*$|$" +./report_coverage_all.sh | grep -v -E "^(cent|com|devi|moni|serv|test)" | grep --color -E -i "^context/.*$|$" diff --git a/run_local_tests.sh b/run_local_tests.sh index c1fb23861b43a29a82a51e30f9476076552fe631..2b2eb9e39d6a8ed8fbbc0a575eb995e8b43ac078 100755 --- a/run_local_tests.sh +++ b/run_local_tests.sh @@ -15,30 +15,32 @@ cat $PROJECTDIR/coverage/.coveragerc.template | sed s+~/teraflow/controller+$PRO rm -f $COVERAGEFILE coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - common/database/tests/test_unitary.py \ - common/database/tests/test_engine_inmemory.py + common/metrics/tests/test_unitary.py \ + common/orm/tests/test_unitary.py -coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - centralizedcybersecurity/tests/test_unitary.py - -coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - context/tests/test_unitary.py - -coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - device/tests/test_unitary_driverapi.py \ - device/tests/test_unitary_service.py - -coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - service/tests/test_unitary.py - -coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - compute/tests/test_unitary.py +#coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ +# centralizedcybersecurity/tests/test_unitary.py -# Run integration tests and analyze coverage of code at same time -export DB_ENGINE='redis' -export REDIS_SERVICE_HOST='10.1.7.194' -export REDIS_SERVICE_PORT='31789' -export REDIS_DATABASE_ID='0' coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ - common/database/tests/test_engine_redis.py \ - tester_integration/test_context_device_service.py + context/tests/test_unitary_orm_context_inmemory.py \ +# context/tests/test_unitary.py + +#coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ +# device/tests/test_unitary_driverapi.py \ +# device/tests/test_unitary_service.py + +#coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ +# service/tests/test_unitary.py + +#coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ +# compute/tests/test_unitary.py + +## Run integration tests and analyze coverage of code at same time +#export DB_ENGINE='redis' +#export REDIS_SERVICE_HOST='10.1.7.194' +## Find exposed port for Redis service port 6379 +#export REDIS_SERVICE_PORT=$(kubectl --namespace gitlab-ci get service redis-public -o 'jsonpath={.spec.ports[?(@.port==6379)].nodePort}') +#export REDIS_DATABASE_ID='0' +#coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ +# context/tests/test_database_engine_redis.py \ +# tester_integration/test_context_device_service.py diff --git a/src/common/metrics/Metrics.py b/src/common/metrics/Metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..33b52f73b62f240167a9724c102785ebad3332bd --- /dev/null +++ b/src/common/metrics/Metrics.py @@ -0,0 +1,66 @@ +import grpc, logging +from enum import Enum +from typing import Dict, List +from prometheus_client import Counter, Histogram +from prometheus_client.metrics import MetricWrapperBase +from common.exceptions.ServiceException import ServiceException + +class RequestConditionEnum(Enum): + STARTED = 'started' + COMPLETED = 'completed' + FAILED = 'failed' + +def get_counter_requests(method_name : str, request_condition : RequestConditionEnum) -> Counter: + str_request_condition = request_condition.value + name = '{:s}_counter_requests_{:s}'.format(method_name.replace(':', '_'), str_request_condition) + description = '{:s} counter of requests {:s}'.format(method_name, str_request_condition) + return Counter(name, description) + +def get_histogram_duration(method_name : str) -> Histogram: + name = '{:s}_histogram_duration'.format(method_name.replace(':', '_')) + description = '{:s} histogram of request duration'.format(method_name) + return Histogram(name, description) + +METRIC_TEMPLATES = { + '{:s}_COUNTER_STARTED' : lambda method_name: get_counter_requests (method_name, RequestConditionEnum.STARTED), + '{:s}_COUNTER_COMPLETED' : lambda method_name: get_counter_requests (method_name, RequestConditionEnum.COMPLETED), + '{:s}_COUNTER_FAILED' : lambda method_name: get_counter_requests (method_name, RequestConditionEnum.FAILED), + '{:s}_HISTOGRAM_DURATION': lambda method_name: get_histogram_duration(method_name), +} + +def create_metrics(service_name : str, method_names : List[str]) -> Dict[str, MetricWrapperBase]: + metrics = {} + for method_name in method_names: + for template_name, template_generator_function in METRIC_TEMPLATES.items(): + metric_name = template_name.format(method_name).upper() + metrics[metric_name] = template_generator_function('{:s}:{:s}'.format(service_name, method_name)) + return metrics + +def safe_and_metered_rpc_method(metrics : Dict[str, MetricWrapperBase], logger : logging.Logger): + def outer_wrapper(func): + function_name = func.__name__ + print('function_name', function_name) + HISTOGRAM_DURATION : Histogram = metrics.get('{:s}_HISTOGRAM_DURATION'.format(function_name).upper()) + COUNTER_STARTED : Counter = metrics.get('{:s}_COUNTER_STARTED' .format(function_name).upper()) + COUNTER_COMPLETED : Counter = metrics.get('{:s}_COUNTER_COMPLETED' .format(function_name).upper()) + COUNTER_FAILED : Counter = metrics.get('{:s}_COUNTER_FAILED' .format(function_name).upper()) + + @HISTOGRAM_DURATION.time() + def inner_wrapper(self, request, grpc_context : grpc.ServicerContext): + COUNTER_STARTED.inc() + try: + logger.debug('{:s} request: {:s}'.format(function_name, str(request))) + reply = func(self, request, grpc_context) + logger.debug('{:s} reply: {:s}'.format(function_name, str(reply))) + COUNTER_COMPLETED.inc() + return reply + except ServiceException as e: # pragma: no cover (ServiceException not thrown) + logger.exception('{:s} exception'.format(function_name)) + COUNTER_FAILED.inc() + grpc_context.abort(e.code, e.details) + except Exception as e: # pragma: no cover + logger.exception('{:s} exception'.format(function_name)) + COUNTER_FAILED.inc() + grpc_context.abort(grpc.StatusCode.INTERNAL, str(e)) + return inner_wrapper + return outer_wrapper diff --git a/src/common/metrics/__init__.py b/src/common/metrics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/common/metrics/tests/__init__.py b/src/common/metrics/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/common/metrics/tests/test_unitary.py b/src/common/metrics/tests/test_unitary.py new file mode 100644 index 0000000000000000000000000000000000000000..cfc755aa995834546bf11ab6b2a59f989de0d1c5 --- /dev/null +++ b/src/common/metrics/tests/test_unitary.py @@ -0,0 +1,30 @@ +import grpc, logging, pytest, time +from common.metrics.Metrics import create_metrics, safe_and_metered_rpc_method + +logging.basicConfig(level=logging.DEBUG) +LOGGER = logging.getLogger(__name__) + +def test_database_instantiation(): + SERVICE_NAME = 'Context' + METHOD_NAMES = [ + 'ListContextIds', 'ListContexts', 'GetContext', 'SetContext', 'RemoveContext', 'GetContextEvents', + 'ListTopologyIds', 'ListTopologies', 'GetTopology', 'SetTopology', 'RemoveTopology', 'GetTopologyEvents', + 'ListDeviceIds', 'ListDevices', 'GetDevice', 'SetDevice', 'RemoveDevice', 'GetDeviceEvents', + 'ListLinkIds', 'ListLinks', 'GetLink', 'SetLink', 'RemoveLink', 'GetLinkEvents', + 'ListServiceIds', 'ListServices', 'GetService', 'SetService', 'RemoveService', 'GetServiceEvents', + ] + METRICS = create_metrics(SERVICE_NAME, METHOD_NAMES) + + class TestServiceServicerImpl: + @safe_and_metered_rpc_method(METRICS, LOGGER) + def GetTopology(self, request, grpc_context : grpc.ServicerContext): + print('doing funny things') + time.sleep(0.1) + return 'done' + + tssi = TestServiceServicerImpl() + tssi.GetTopology(1, 2) + + for metric_name,metric in METRICS.items(): + if 'GETTOPOLOGY_' not in metric_name: continue + print(metric_name, metric._child_samples()) diff --git a/src/common/orm/tests/test_unitary_orm.py b/src/common/orm/tests/test_unitary.py similarity index 100% rename from src/common/orm/tests/test_unitary_orm.py rename to src/common/orm/tests/test_unitary.py