diff --git a/deploy/tfs.sh b/deploy/tfs.sh index facba7cfd85abfc6a615528d837c37e09406f9d5..fd8625cf2563280e8e6a8ff7a714a03a8622a473 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -428,7 +428,7 @@ if [[ "$TFS_COMPONENTS" == *"webui"* ]]; then curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{ "access" : "proxy", "type" : "prometheus", - "name" : "Prometheus", + "name" : "prometheus", "url" : "http://prometheus-k8s.monitoring.svc:9090", "basicAuth": false, "isDefault": false, @@ -438,23 +438,68 @@ if [[ "$TFS_COMPONENTS" == *"webui"* ]]; then }' ${GRAFANA_URL_UPDATED}/api/datasources printf "\n\n" - echo ">> Creating dashboards..." + echo ">> Creating and staring dashboards..." # Ref: https://grafana.com/docs/grafana/latest/http_api/dashboard/ + + # Dashboard: L3 Monitoring KPIs curl -X POST -H "Content-Type: application/json" -d '@src/webui/grafana_db_mon_kpis_psql.json' \ ${GRAFANA_URL_UPDATED}/api/dashboards/db echo + DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-l3-monit" + DASHBOARD_ID=$(curl -s "${DASHBOARD_URL}" | jq '.dashboard.id') + curl -X POST ${GRAFANA_URL_UPDATED}/api/user/stars/dashboard/${DASHBOARD_ID} + echo + # Dashboard: Slice Grouping curl -X POST -H "Content-Type: application/json" -d '@src/webui/grafana_db_slc_grps_psql.json' \ ${GRAFANA_URL_UPDATED}/api/dashboards/db - printf "\n\n" + echo + DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-slice-grps" + DASHBOARD_ID=$(curl -s "${DASHBOARD_URL}" | jq '.dashboard.id') + curl -X POST ${GRAFANA_URL_UPDATED}/api/user/stars/dashboard/${DASHBOARD_ID} + echo - echo ">> Staring dashboards..." - DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-l3-monit" + # Dashboard: Component RPCs + curl -X POST -H "Content-Type: application/json" -d '@src/webui/grafana_prom_component_rpc.json' \ + ${GRAFANA_URL_UPDATED}/api/dashboards/db + echo + DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-comp-rpc" DASHBOARD_ID=$(curl -s "${DASHBOARD_URL}" | jq '.dashboard.id') curl -X POST ${GRAFANA_URL_UPDATED}/api/user/stars/dashboard/${DASHBOARD_ID} echo - DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-slice-grps" + # Dashboard: Device Drivers + curl -X POST -H "Content-Type: application/json" -d '@src/webui/grafana_prom_device_driver.json' \ + ${GRAFANA_URL_UPDATED}/api/dashboards/db + echo + DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-dev-drv" + DASHBOARD_ID=$(curl -s "${DASHBOARD_URL}" | jq '.dashboard.id') + curl -X POST ${GRAFANA_URL_UPDATED}/api/user/stars/dashboard/${DASHBOARD_ID} + echo + + # Dashboard: Service Handlers + curl -X POST -H "Content-Type: application/json" -d '@src/webui/grafana_prom_service_handler.json' \ + ${GRAFANA_URL_UPDATED}/api/dashboards/db + echo + DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-svc-hdlr" + DASHBOARD_ID=$(curl -s "${DASHBOARD_URL}" | jq '.dashboard.id') + curl -X POST ${GRAFANA_URL_UPDATED}/api/user/stars/dashboard/${DASHBOARD_ID} + echo + + # Dashboard: Device Execution Details + curl -X POST -H "Content-Type: application/json" -d '@src/webui/grafana_prom_device_exec_details.json' \ + ${GRAFANA_URL_UPDATED}/api/dashboards/db + echo + DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-dev-exec" + DASHBOARD_ID=$(curl -s "${DASHBOARD_URL}" | jq '.dashboard.id') + curl -X POST ${GRAFANA_URL_UPDATED}/api/user/stars/dashboard/${DASHBOARD_ID} + echo + + # Dashboard: Load Generator Status + curl -X POST -H "Content-Type: application/json" -d '@src/webui/grafana_prom_load_generator.json' \ + ${GRAFANA_URL_UPDATED}/api/dashboards/db + echo + DASHBOARD_URL="${GRAFANA_URL_UPDATED}/api/dashboards/uid/tfs-loadgen-stats" DASHBOARD_ID=$(curl -s "${DASHBOARD_URL}" | jq '.dashboard.id') curl -X POST ${GRAFANA_URL_UPDATED}/api/user/stars/dashboard/${DASHBOARD_ID} echo diff --git a/manifests/cockroachdb/client-secure-operator.yaml b/manifests/cockroachdb/client-secure-operator.yaml index f7f81c8339d4ba47722a0ef2a2236178f1b9e1b0..ee3afbc5ae5feec673dc5f507f8bc794757818c7 100644 --- a/manifests/cockroachdb/client-secure-operator.yaml +++ b/manifests/cockroachdb/client-secure-operator.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 The Cockroach Authors +# Copyright 2023 The Cockroach Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ spec: serviceAccountName: cockroachdb-sa containers: - name: cockroachdb-client-secure - image: cockroachdb/cockroach:v22.2.0 + image: cockroachdb/cockroach:v22.2.8 imagePullPolicy: IfNotPresent volumeMounts: - name: client-certs diff --git a/manifests/cockroachdb/cluster.yaml b/manifests/cockroachdb/cluster.yaml index b618c6f17c2e00b9ff74509955b060c67ea3d2cc..4d9ef0f844b5ffb02753b6cc7a7be7d03928896c 100644 --- a/manifests/cockroachdb/cluster.yaml +++ b/manifests/cockroachdb/cluster.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 The Cockroach Authors +# Copyright 2023 The Cockroach Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,17 +33,16 @@ spec: resources: requests: # This is intentionally low to make it work on local k3d clusters. - cpu: 100m - memory: 1Gi - limits: - cpu: 1 + cpu: 4 memory: 4Gi + limits: + cpu: 8 + memory: 8Gi tlsEnabled: true # You can set either a version of the db or a specific image name -# cockroachDBVersion: v22.2.0 +# cockroachDBVersion: v22.2.8 image: - #name: cockroachdb/cockroach:v22.2.0 - name: cockroachdb/cockroach:latest-v22.2 + name: cockroachdb/cockroach:v22.2.8 # nodes refers to the number of crdb pods that are created # via the statefulset nodes: 3 diff --git a/manifests/cockroachdb/crds.yaml b/manifests/cockroachdb/crds.yaml index 1b5cd89ae7001b3e200c0de7da240b660c461f3b..2ef9983924f639a82d2091907384be18e6c8c1f4 100644 --- a/manifests/cockroachdb/crds.yaml +++ b/manifests/cockroachdb/crds.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 The Cockroach Authors +# Copyright 2023 The Cockroach Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -354,10 +354,71 @@ spec: The requirements are ANDed. type: object type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. This field is alpha-level + and is only honored when PodAffinityNamespaceSelector + feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace" items: type: string type: array @@ -449,10 +510,66 @@ spec: requirements are ANDed. type: object type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied to the + union of the namespaces selected by this field and + the ones listed in the namespaces field. null selector + and null or empty namespaces list means "this pod's + namespace". An empty selector ({}) matches all namespaces. + This field is alpha-level and is only honored when + PodAffinityNamespaceSelector feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" + description: namespaces specifies a static list of namespace + names that the term applies to. The term is applied + to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. null or + empty namespaces list and null namespaceSelector means + "this pod's namespace" items: type: string type: array @@ -546,10 +663,71 @@ spec: The requirements are ANDed. type: object type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. This field is alpha-level + and is only honored when PodAffinityNamespaceSelector + feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object namespaces: - description: namespaces specifies which namespaces - the labelSelector applies to (matches against); - null or empty list means "this pod's namespace" + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace" items: type: string type: array @@ -641,10 +819,66 @@ spec: requirements are ANDed. type: object type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied to the + union of the namespaces selected by this field and + the ones listed in the namespaces field. null selector + and null or empty namespaces list means "this pod's + namespace". An empty selector ({}) matches all namespaces. + This field is alpha-level and is only honored when + PodAffinityNamespaceSelector feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object namespaces: - description: namespaces specifies which namespaces the - labelSelector applies to (matches against); null or - empty list means "this pod's namespace" + description: namespaces specifies a static list of namespace + names that the term applies to. The term is applied + to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. null or + empty namespaces list and null namespaceSelector means + "this pod's namespace" items: type: string type: array @@ -767,7 +1001,7 @@ spec: pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Limits describes the maximum amount - of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object requests: additionalProperties: @@ -780,7 +1014,7 @@ spec: of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object selector: @@ -1138,7 +1372,7 @@ spec: pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object requests: additionalProperties: @@ -1150,7 +1384,7 @@ spec: description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object sqlPort: diff --git a/manifests/cockroachdb/operator.yaml b/manifests/cockroachdb/operator.yaml index 2be72d329b48bc6f45d66f811c299140cda85e27..59d515061c4c0f253523aab803653b3f33007461 100644 --- a/manifests/cockroachdb/operator.yaml +++ b/manifests/cockroachdb/operator.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 The Cockroach Authors +# Copyright 2023 The Cockroach Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -538,8 +538,34 @@ spec: value: cockroachdb/cockroach:v22.1.11 - name: RELATED_IMAGE_COCKROACH_v22_1_12 value: cockroachdb/cockroach:v22.1.12 + - name: RELATED_IMAGE_COCKROACH_v22_1_13 + value: cockroachdb/cockroach:v22.1.13 + - name: RELATED_IMAGE_COCKROACH_v22_1_14 + value: cockroachdb/cockroach:v22.1.14 + - name: RELATED_IMAGE_COCKROACH_v22_1_15 + value: cockroachdb/cockroach:v22.1.15 + - name: RELATED_IMAGE_COCKROACH_v22_1_16 + value: cockroachdb/cockroach:v22.1.16 + - name: RELATED_IMAGE_COCKROACH_v22_1_18 + value: cockroachdb/cockroach:v22.1.18 - name: RELATED_IMAGE_COCKROACH_v22_2_0 value: cockroachdb/cockroach:v22.2.0 + - name: RELATED_IMAGE_COCKROACH_v22_2_1 + value: cockroachdb/cockroach:v22.2.1 + - name: RELATED_IMAGE_COCKROACH_v22_2_2 + value: cockroachdb/cockroach:v22.2.2 + - name: RELATED_IMAGE_COCKROACH_v22_2_3 + value: cockroachdb/cockroach:v22.2.3 + - name: RELATED_IMAGE_COCKROACH_v22_2_4 + value: cockroachdb/cockroach:v22.2.4 + - name: RELATED_IMAGE_COCKROACH_v22_2_5 + value: cockroachdb/cockroach:v22.2.5 + - name: RELATED_IMAGE_COCKROACH_v22_2_6 + value: cockroachdb/cockroach:v22.2.6 + - name: RELATED_IMAGE_COCKROACH_v22_2_7 + value: cockroachdb/cockroach:v22.2.7 + - name: RELATED_IMAGE_COCKROACH_v22_2_8 + value: cockroachdb/cockroach:v22.2.8 - name: OPERATOR_NAME value: cockroachdb - name: WATCH_NAMESPACE @@ -552,7 +578,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: cockroachdb/cockroach-operator:v2.9.0 + image: cockroachdb/cockroach-operator:v2.10.0 imagePullPolicy: IfNotPresent name: cockroach-operator resources: diff --git a/manifests/load_generatorservice.yaml b/manifests/load_generatorservice.yaml index 3f65c2c857a39f2b7a5ebeaccd9ddfd4916f2487..7cc6f19122573a612ddca774c3a785bff93f8b38 100644 --- a/manifests/load_generatorservice.yaml +++ b/manifests/load_generatorservice.yaml @@ -33,6 +33,7 @@ spec: imagePullPolicy: Always ports: - containerPort: 50052 + - containerPort: 9192 env: - name: LOG_LEVEL value: "INFO" @@ -65,3 +66,7 @@ spec: protocol: TCP port: 50052 targetPort: 50052 + - name: metrics + protocol: TCP + port: 9192 + targetPort: 9192 diff --git a/manifests/servicemonitors.yaml b/manifests/servicemonitors.yaml index f5da08182a4665b21607987ea97d9bf3cc5b7e21..ec929f757cdf5468a7db7a7c1f1e755611d5327b 100644 --- a/manifests/servicemonitors.yaml +++ b/manifests/servicemonitors.yaml @@ -330,3 +330,32 @@ spec: any: false matchNames: - tfs # namespace where the app is running +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + namespace: monitoring # namespace where prometheus is running + name: tfs-load-generatorservice-metric + labels: + app: load-generatorservice + #release: prometheus + #release: prom # name of the release + # ( VERY IMPORTANT: You need to know the correct release name by viewing + # the servicemonitor of Prometheus itself: Without the correct name, + # Prometheus cannot identify the metrics of the Flask app as the target.) +spec: + selector: + matchLabels: + # Target app service + #namespace: tfs + app: load-generatorservice # same as above + #release: prometheus # same as above + endpoints: + - port: metrics # named port in target app + scheme: http + path: /metrics # path to scrape + interval: 5s # scrape interval + namespaceSelector: + any: false + matchNames: + - tfs # namespace where the app is running diff --git a/proto/load_generator.proto b/proto/load_generator.proto index 86f9469588f1586da5339edad198e39e82598cde..32523b331418813b51fb542d9eb17e29fc2b13d2 100644 --- a/proto/load_generator.proto +++ b/proto/load_generator.proto @@ -33,21 +33,40 @@ enum RequestTypeEnum { REQUESTTYPE_SLICE_L3NM = 6; } +message Range { + float minimum = 1; + float maximum = 2; +} + +message ScalarOrRange { + oneof value { + float scalar = 1; // select the scalar value + Range range = 2; // select a random uniformly dstributed value between minimum and maximum + } +} + message Parameters { uint64 num_requests = 1; // if == 0, generate infinite requests repeated RequestTypeEnum request_types = 2; - float offered_load = 3; - float holding_time = 4; - float inter_arrival_time = 5; - bool do_teardown = 6; - bool dry_mode = 7; - bool record_to_dlt = 8; - string dlt_domain_id = 9; + string device_regex = 3; // Only devices and endpoints matching the regex expression will be considered as + string endpoint_regex = 4; // source-destination candidates for the requests generated. + float offered_load = 5; + float holding_time = 6; + float inter_arrival_time = 7; + repeated ScalarOrRange availability = 8; // One from the list is selected to populate the constraint + repeated ScalarOrRange capacity_gbps = 9; // One from the list is selected to populate the constraint + repeated ScalarOrRange e2e_latency_ms = 10; // One from the list is selected to populate the constraint + uint32 max_workers = 11; + bool do_teardown = 12; + bool dry_mode = 13; + bool record_to_dlt = 14; + string dlt_domain_id = 15; } message Status { Parameters parameters = 1; uint64 num_generated = 2; - bool infinite_loop = 3; - bool running = 4; + uint64 num_released = 3; + bool infinite_loop = 4; + bool running = 5; } diff --git a/src/common/method_wrappers/Decorator.py b/src/common/method_wrappers/Decorator.py index a5db54125518933199c27662bc0c044988d1daac..b241d3b62821c0bfe319546cbeadce79fce59db9 100644 --- a/src/common/method_wrappers/Decorator.py +++ b/src/common/method_wrappers/Decorator.py @@ -15,7 +15,7 @@ import grpc, json, logging, threading from enum import Enum from prettytable import PrettyTable -from typing import Any, Dict, List, Set, Tuple +from typing import Any, Dict, List, Optional, Set, Tuple from prometheus_client import Counter, Histogram from prometheus_client.metrics import MetricWrapperBase, INF from common.tools.grpc.Tools import grpc_message_to_json_string @@ -25,12 +25,14 @@ class MetricTypeEnum(Enum): COUNTER_STARTED = 'tfs_{component:s}_{sub_module:s}_{method:s}_counter_requests_started' COUNTER_COMPLETED = 'tfs_{component:s}_{sub_module:s}_{method:s}_counter_requests_completed' COUNTER_FAILED = 'tfs_{component:s}_{sub_module:s}_{method:s}_counter_requests_failed' + COUNTER_BLOCKED = 'tfs_{component:s}_{sub_module:s}_{method:s}_counter_requests_blocked' HISTOGRAM_DURATION = 'tfs_{component:s}_{sub_module:s}_{method:s}_histogram_duration' METRIC_TO_CLASS_PARAMS = { MetricTypeEnum.COUNTER_STARTED : (Counter, {}), MetricTypeEnum.COUNTER_COMPLETED : (Counter, {}), MetricTypeEnum.COUNTER_FAILED : (Counter, {}), + MetricTypeEnum.COUNTER_BLOCKED : (Counter, {}), MetricTypeEnum.HISTOGRAM_DURATION: (Histogram, { 'buckets': ( # .005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, INF @@ -75,21 +77,45 @@ class MetricsPool: return MetricsPool.metrics[metric_name] def get_metrics( - self, method : str - ) -> Tuple[MetricWrapperBase, MetricWrapperBase, MetricWrapperBase, MetricWrapperBase]: + self, method : str, labels : Optional[Dict[str, str]] = None + ) -> Tuple[Histogram, Counter, Counter, Counter]: histogram_duration : Histogram = self.get_or_create(method, MetricTypeEnum.HISTOGRAM_DURATION) counter_started : Counter = self.get_or_create(method, MetricTypeEnum.COUNTER_STARTED) counter_completed : Counter = self.get_or_create(method, MetricTypeEnum.COUNTER_COMPLETED) counter_failed : Counter = self.get_or_create(method, MetricTypeEnum.COUNTER_FAILED) - if len(self._labels) > 0: - histogram_duration = histogram_duration.labels(**(self._labels)) - counter_started = counter_started.labels(**(self._labels)) - counter_completed = counter_completed.labels(**(self._labels)) - counter_failed = counter_failed.labels(**(self._labels)) + if labels is None and len(self._labels) > 0: + labels = self._labels + + if labels is not None and len(labels) > 0: + histogram_duration = histogram_duration.labels(**labels) + counter_started = counter_started.labels(**labels) + counter_completed = counter_completed.labels(**labels) + counter_failed = counter_failed.labels(**labels) return histogram_duration, counter_started, counter_completed, counter_failed + def get_metrics_loadgen( + self, method : str, labels : Optional[Dict[str, str]] = None + ) -> Tuple[Histogram, Counter, Counter, Counter, Counter]: + histogram_duration : Histogram = self.get_or_create(method, MetricTypeEnum.HISTOGRAM_DURATION) + counter_started : Counter = self.get_or_create(method, MetricTypeEnum.COUNTER_STARTED) + counter_completed : Counter = self.get_or_create(method, MetricTypeEnum.COUNTER_COMPLETED) + counter_failed : Counter = self.get_or_create(method, MetricTypeEnum.COUNTER_FAILED) + counter_blocked : Counter = self.get_or_create(method, MetricTypeEnum.COUNTER_BLOCKED) + + if labels is None and len(self._labels) > 0: + labels = self._labels + + if labels is not None and len(labels) > 0: + histogram_duration = histogram_duration.labels(**labels) + counter_started = counter_started.labels(**labels) + counter_completed = counter_completed.labels(**labels) + counter_failed = counter_failed.labels(**labels) + counter_blocked = counter_blocked.labels(**labels) + + return histogram_duration, counter_started, counter_completed, counter_failed, counter_blocked + def get_pretty_table(self, remove_empty_buckets : bool = True) -> PrettyTable: with MetricsPool.lock: method_to_metric_fields : Dict[str, Dict[str, Dict[str, Any]]] = dict() @@ -200,6 +226,8 @@ def safe_and_metered_rpc_method(metrics_pool : MetricsPool, logger : logging.Log # Assume not found or already exists is just a condition, not an error logger.exception('{:s} exception'.format(method_name)) counter_failed.inc() + else: + counter_completed.inc() grpc_context.abort(e.code, e.details) except Exception as e: # pragma: no cover, pylint: disable=broad-except logger.exception('{:s} exception'.format(method_name)) diff --git a/src/common/method_wrappers/tests/grafana_prometheus_component_rpc.json b/src/common/method_wrappers/tests/grafana_prometheus_component_rpc.json deleted file mode 100644 index b5b857e7573264f26289ba9a72ec5444e4ac71a4..0000000000000000000000000000000000000000 --- a/src/common/method_wrappers/tests/grafana_prometheus_component_rpc.json +++ /dev/null @@ -1,426 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 25, - "iteration": 1671297223428, - "links": [], - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 0 - }, - "hiddenSeries": false, - "id": 4, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(tfs_[[component]]_rpc_[[method]]_counter_requests_started_total{pod=~\"[[pod]]\"})", - "interval": "", - "legendFormat": "started", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "sum(tfs_[[component]]_rpc_[[method]]_counter_requests_completed_total{pod=~\"[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "completed", - "refId": "B" - }, - { - "exemplar": true, - "expr": "sum(tfs_[[component]]_rpc_[[method]]_counter_requests_started_total{pod=~\"[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "failed", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Requests", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:935", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "$$hashKey": "object:936", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "cards": { - "cardPadding": null, - "cardRound": null - }, - "color": { - "cardColor": "#b4ff00", - "colorScale": "linear", - "colorScheme": "interpolateRdYlGn", - "exponent": 0.5, - "max": null, - "min": 0, - "mode": "opacity" - }, - "dataFormat": "tsbuckets", - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 6 - }, - "heatmap": {}, - "hideZeroBuckets": true, - "highlightCards": true, - "id": 2, - "interval": "60s", - "legend": { - "show": true - }, - "pluginVersion": "7.5.4", - "reverseYBuckets": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(\r\n max_over_time(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket{pod=~\"[[pod]]\"}[1m]) -\r\n min_over_time(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket{pod=~\"[[pod]]\"}[1m])\r\n) by (le)", - "format": "heatmap", - "instant": false, - "interval": "1m", - "intervalFactor": 1, - "legendFormat": "{{le}}", - "queryType": "randomWalk", - "refId": "A" - } - ], - "title": "Histogram", - "tooltip": { - "show": true, - "showHistogram": true - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "xBucketNumber": null, - "xBucketSize": null, - "yAxis": { - "decimals": null, - "format": "s", - "logBase": 1, - "max": null, - "min": null, - "show": true, - "splitFactor": null - }, - "yBucketBound": "auto", - "yBucketNumber": null, - "yBucketSize": null - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 14 - }, - "hiddenSeries": false, - "id": 5, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(tfs_[[component]]_rpc_[[method]]_histogram_duration_sum{pod=~\"[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "total time", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Total Exec Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:407", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "$$hashKey": "object:408", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "5s", - "schemaVersion": 27, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "selected": false, - "text": "context", - "value": "context" - }, - "datasource": "prometheus", - "definition": "metrics(tfs_)", - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Component", - "multi": false, - "name": "component", - "options": [], - "query": { - "query": "metrics(tfs_)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "/tfs_(.+)_rpc_.*/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": "", - "current": { - "selected": false, - "text": "getcontext", - "value": "getcontext" - }, - "datasource": "prometheus", - "definition": "metrics(tfs_[[component]]_rpc_)", - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Method", - "multi": false, - "name": "method", - "options": [], - "query": { - "query": "metrics(tfs_[[component]]_rpc_)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "/tfs_[[component]]_rpc_(.+)_histogram_duration_bucket/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "prometheus", - "definition": "label_values(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket, pod)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Pod", - "multi": true, - "name": "pod", - "options": [], - "query": { - "query": "label_values(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket, pod)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "TFS / Component RPCs", - "uid": "KKxzxIFVz", - "version": 21 -} \ No newline at end of file diff --git a/src/common/method_wrappers/tests/grafana_prometheus_device_config_exec_details.json b/src/common/method_wrappers/tests/grafana_prometheus_device_config_exec_details.json deleted file mode 100644 index 980b9583a898f8fb2d84c22329ce8053e120b7f8..0000000000000000000000000000000000000000 --- a/src/common/method_wrappers/tests/grafana_prometheus_device_config_exec_details.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 30, - "iteration": 1682003744753, - "links": [], - "panels": [ - { - "cards": { - "cardPadding": null, - "cardRound": null - }, - "color": { - "cardColor": "#b4ff00", - "colorScale": "linear", - "colorScheme": "interpolateRdYlGn", - "exponent": 0.5, - "max": null, - "min": 0, - "mode": "opacity" - }, - "dataFormat": "tsbuckets", - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 22, - "w": 24, - "x": 0, - "y": 0 - }, - "heatmap": {}, - "hideZeroBuckets": true, - "highlightCards": true, - "id": 2, - "interval": "60s", - "legend": { - "show": true - }, - "pluginVersion": "7.5.4", - "reverseYBuckets": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(\r\n max_over_time(tfs_device_exec_details_configuredevice_histogram_duration_bucket{pod=~\"[[pod]]\", step_name=~\"[[step_name]]\"}[1m]) -\r\n min_over_time(tfs_device_exec_details_configuredevice_histogram_duration_bucket{pod=~\"[[pod]]\", step_name=~\"[[step_name]]\"}[1m])\r\n) by (le)", - "format": "heatmap", - "instant": false, - "interval": "1m", - "intervalFactor": 1, - "legendFormat": "{{le}}", - "queryType": "randomWalk", - "refId": "A" - } - ], - "title": "Histogram", - "tooltip": { - "show": true, - "showHistogram": true - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "xBucketNumber": null, - "xBucketSize": null, - "yAxis": { - "decimals": null, - "format": "s", - "logBase": 1, - "max": null, - "min": null, - "show": true, - "splitFactor": null - }, - "yBucketBound": "auto", - "yBucketNumber": null, - "yBucketSize": null - } - ], - "refresh": false, - "schemaVersion": 27, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": ".*", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "prometheus", - "definition": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, pod)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Pod", - "multi": true, - "name": "pod", - "options": [], - "query": { - "query": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, pod)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": { - "selected": true, - "text": [ - "get_device", - "set_device" - ], - "value": [ - "get_device", - "set_device" - ] - }, - "datasource": "prometheus", - "definition": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, step_name)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Step Name", - "multi": true, - "name": "step_name", - "options": [], - "query": { - "query": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, step_name)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "2023-04-20T15:11:48.272Z", - "to": "2023-04-20T15:18:10.464Z" - }, - "timepicker": {}, - "timezone": "", - "title": "TFS / ConfigureDevice Details", - "uid": "GfzKHbPVk", - "version": 4 -} \ No newline at end of file diff --git a/src/common/method_wrappers/tests/grafana_prometheus_device_driver.json b/src/common/method_wrappers/tests/grafana_prometheus_device_driver.json deleted file mode 100644 index 2926a409b3b77b16c4e7b5d86ecd7d56f6acdebc..0000000000000000000000000000000000000000 --- a/src/common/method_wrappers/tests/grafana_prometheus_device_driver.json +++ /dev/null @@ -1,431 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 26, - "iteration": 1671318718779, - "links": [], - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 0 - }, - "hiddenSeries": false, - "id": 4, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(tfs_device_driver_[[method]]_counter_requests_started_total{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", - "interval": "", - "legendFormat": "started", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "sum(tfs_device_driver_[[method]]_counter_requests_completed_total{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "completed", - "refId": "B" - }, - { - "exemplar": true, - "expr": "sum(tfs_device_driver_[[method]]_counter_requests_failed_total{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "failed", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Requests", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:864", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "$$hashKey": "object:865", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "cards": { - "cardPadding": null, - "cardRound": null - }, - "color": { - "cardColor": "#b4ff00", - "colorScale": "linear", - "colorScheme": "interpolateRdYlGn", - "exponent": 0.5, - "max": null, - "min": 0, - "mode": "opacity" - }, - "dataFormat": "tsbuckets", - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 6 - }, - "heatmap": {}, - "hideZeroBuckets": true, - "highlightCards": true, - "id": 2, - "interval": "60s", - "legend": { - "show": true - }, - "pluginVersion": "7.5.4", - "reverseYBuckets": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(\r\n max_over_time(tfs_device_driver_[[method]]_histogram_duration_bucket{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"}[1m]) -\r\n min_over_time(tfs_device_driver_[[method]]_histogram_duration_bucket{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"}[1m])\r\n) by (le)", - "format": "heatmap", - "instant": false, - "interval": "60s", - "intervalFactor": 1, - "legendFormat": "{{le}}", - "queryType": "randomWalk", - "refId": "A" - } - ], - "timeFrom": null, - "title": "Histogram", - "tooltip": { - "show": true, - "showHistogram": true - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "xBucketNumber": null, - "xBucketSize": null, - "yAxis": { - "decimals": null, - "format": "s", - "logBase": 1, - "max": null, - "min": null, - "show": true, - "splitFactor": null - }, - "yBucketBound": "auto", - "yBucketNumber": null, - "yBucketSize": null - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 14 - }, - "hiddenSeries": false, - "id": 5, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(tfs_device_driver_[[method]]_histogram_duration_sum{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "total time", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Total Exec Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:407", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "$$hashKey": "object:408", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "5s", - "schemaVersion": 27, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": "", - "current": { - "selected": false, - "text": "setconfig", - "value": "setconfig" - }, - "datasource": "prometheus", - "definition": "metrics(tfs_device_driver_.+)", - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Method", - "multi": false, - "name": "method", - "options": [], - "query": { - "query": "metrics(tfs_device_driver_.+)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "/tfs_device_driver_(.+config)_histogram_duration_bucket/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "prometheus", - "definition": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, driver)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Driver", - "multi": true, - "name": "driver", - "options": [], - "query": { - "query": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, driver)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "prometheus", - "definition": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, pod)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Pod", - "multi": true, - "name": "pod", - "options": [], - "query": { - "query": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, pod)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "/deviceservice-(.*)/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "TFS / Device / Driver", - "uid": "eAg-wsOVk", - "version": 30 -} \ No newline at end of file diff --git a/src/common/method_wrappers/tests/grafana_prometheus_service_handler.json b/src/common/method_wrappers/tests/grafana_prometheus_service_handler.json deleted file mode 100644 index 48e770afe4bba9c2eb5df76d3532bf35d6cfe192..0000000000000000000000000000000000000000 --- a/src/common/method_wrappers/tests/grafana_prometheus_service_handler.json +++ /dev/null @@ -1,432 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": 27, - "iteration": 1671319012315, - "links": [], - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 0 - }, - "hiddenSeries": false, - "id": 4, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(tfs_service_handler_[[method]]_counter_requests_started_total{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", - "instant": false, - "interval": "", - "legendFormat": "started", - "queryType": "randomWalk", - "refId": "A" - }, - { - "exemplar": true, - "expr": "sum(tfs_service_handler_[[method]]_counter_requests_completed_total{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "completed", - "refId": "B" - }, - { - "exemplar": true, - "expr": "sum(tfs_service_handler_[[method]]_counter_requests_failed_total{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "failed", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Requests", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:935", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "$$hashKey": "object:936", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "cards": { - "cardPadding": null, - "cardRound": null - }, - "color": { - "cardColor": "#b4ff00", - "colorScale": "linear", - "colorScheme": "interpolateRdYlGn", - "exponent": 0.5, - "max": null, - "min": 0, - "mode": "opacity" - }, - "dataFormat": "tsbuckets", - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 6 - }, - "heatmap": {}, - "hideZeroBuckets": true, - "highlightCards": true, - "id": 2, - "interval": "60s", - "legend": { - "show": true - }, - "pluginVersion": "7.5.4", - "reverseYBuckets": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(\r\n max_over_time(tfs_service_handler_[[method]]_histogram_duration_bucket{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"}[1m]) -\r\n min_over_time(tfs_service_handler_[[method]]_histogram_duration_bucket{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"}[1m])\r\n) by (le)", - "format": "heatmap", - "instant": false, - "interval": "1m", - "intervalFactor": 1, - "legendFormat": "{{le}}", - "queryType": "randomWalk", - "refId": "A" - } - ], - "timeFrom": null, - "title": "Histogram", - "tooltip": { - "show": true, - "showHistogram": true - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "xBucketNumber": null, - "xBucketSize": null, - "yAxis": { - "decimals": null, - "format": "s", - "logBase": 1, - "max": null, - "min": null, - "show": true, - "splitFactor": null - }, - "yBucketBound": "auto", - "yBucketNumber": null, - "yBucketSize": null - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 14 - }, - "hiddenSeries": false, - "id": 5, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.4", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(tfs_service_handler_[[method]]_histogram_duration_sum{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", - "hide": false, - "interval": "", - "legendFormat": "total time", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Total Exec Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transformations": [], - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:407", - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "$$hashKey": "object:408", - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "5s", - "schemaVersion": 27, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": "", - "current": { - "selected": false, - "text": "setendpoint", - "value": "setendpoint" - }, - "datasource": "prometheus", - "definition": "metrics(tfs_service_handler_.+)", - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Method", - "multi": false, - "name": "method", - "options": [], - "query": { - "query": "metrics(tfs_service_handler_.+)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "/tfs_service_handler_(.+)_histogram_duration_bucket/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "prometheus", - "definition": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, handler)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Handler", - "multi": true, - "name": "handler", - "options": [], - "query": { - "query": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, handler)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "datasource": "prometheus", - "definition": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, pod)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Pod", - "multi": true, - "name": "pod", - "options": [], - "query": { - "query": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, pod)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "/serviceservice-(.*)/", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "TFS / Service / Handler", - "uid": "DNOhOIF4k", - "version": 16 -} \ No newline at end of file diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index e7fec041802cc661b14617a8ebfec0864c738b39..38a6b735b32ee667c3be2f5381df84c40d773c06 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -37,8 +37,8 @@ LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('Device', 'RPC') -METRICS_POOL_DETAILS = MetricsPool('Device', 'exec_details', labels={ - 'step_name': '', +METRICS_POOL_DETAILS = MetricsPool('Device', 'execution', labels={ + 'driver': '', 'operation': '', 'step': '', }) class DeviceServiceServicerImpl(DeviceServiceServicer): @@ -51,11 +51,15 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def AddDevice(self, request : Device, context : grpc.ServicerContext) -> DeviceId: + t0 = time.time() + device_uuid = request.device_id.device_uuid.uuid connection_config_rules = check_connect_rules(request.device_config) check_no_endpoints(request.device_endpoints) + t1 = time.time() + context_client = ContextClient() device = get_device(context_client, device_uuid, rw_copy=True) if device is None: @@ -73,10 +77,15 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): # update device_uuid to honor UUID provided by Context device_uuid = device.device_id.device_uuid.uuid + t2 = time.time() + self.mutex_queues.wait_my_turn(device_uuid) + t3 = time.time() try: driver : _Driver = get_driver(self.driver_instance_cache, device) + t4 = time.time() + errors = [] # Sub-devices and sub-links are exposed by intermediate controllers or represent mgmt links. @@ -86,13 +95,23 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): new_sub_links : Dict[str, Link] = dict() if len(device.device_endpoints) == 0: + t5 = time.time() # created from request, populate endpoints using driver errors.extend(populate_endpoints( device, driver, self.monitoring_loops, new_sub_devices, new_sub_links)) + t6 = time.time() + t_pop_endpoints = t6 - t5 + else: + t_pop_endpoints = None if len(device.device_config.config_rules) == len(connection_config_rules): # created from request, populate config rules using driver + t7 = time.time() errors.extend(populate_config_rules(device, driver)) + t8 = time.time() + t_pop_config_rules = t8 - t7 + else: + t_pop_config_rules = None # TODO: populate components @@ -100,22 +119,60 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): for error in errors: LOGGER.error(error) raise OperationFailedException('AddDevice', extra_details=errors) + t9 = time.time() + device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED device_id = context_client.SetDevice(device) + t10 = time.time() + for sub_device in new_sub_devices.values(): context_client.SetDevice(sub_device) + t11 = time.time() + for sub_links in new_sub_links.values(): context_client.SetLink(sub_links) + t12 = time.time() + # Update endpoint monitoring resources with UUIDs device_with_uuids = get_device( context_client, device_id.device_uuid.uuid, rw_copy=False, include_endpoints=True, include_components=False, include_config_rules=False) populate_endpoint_monitoring_resources(device_with_uuids, self.monitoring_loops) + t13 = time.time() + context_client.close() + + t14 = time.time() + + metrics_labels = dict(driver=driver.name, operation='add_device') + + histogram_duration : Histogram = METRICS_POOL_DETAILS.get_or_create( + 'details', MetricTypeEnum.HISTOGRAM_DURATION) + histogram_duration.labels(step='total' , **metrics_labels).observe(t14-t0) + histogram_duration.labels(step='execution' , **metrics_labels).observe(t14-t3) + histogram_duration.labels(step='endpoint_checks' , **metrics_labels).observe(t1-t0) + histogram_duration.labels(step='get_device' , **metrics_labels).observe(t2-t1) + histogram_duration.labels(step='wait_queue' , **metrics_labels).observe(t3-t2) + histogram_duration.labels(step='get_driver' , **metrics_labels).observe(t4-t3) + histogram_duration.labels(step='set_device' , **metrics_labels).observe(t10-t9) + histogram_duration.labels(step='populate_monit_rsrc', **metrics_labels).observe(t13-t12) + + if t_pop_endpoints is not None: + histogram_duration.labels(step='populate_endpoints', **metrics_labels).observe(t_pop_endpoints) + + if t_pop_config_rules is not None: + histogram_duration.labels(step='populate_config_rules', **metrics_labels).observe(t_pop_config_rules) + + if len(new_sub_devices) > 0: + histogram_duration.labels(step='set_sub_devices', **metrics_labels).observe(t11-t10) + + if len(new_sub_links) > 0: + histogram_duration.labels(step='set_sub_links', **metrics_labels).observe(t12-t11) + return device_id finally: self.mutex_queues.signal_done(device_uuid) @@ -195,16 +252,18 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): t9 = time.time() + metrics_labels = dict(driver=driver.name, operation='configure_device') + histogram_duration : Histogram = METRICS_POOL_DETAILS.get_or_create( - 'ConfigureDevice', MetricTypeEnum.HISTOGRAM_DURATION) - histogram_duration.labels(step_name='total' ).observe(t9-t0) - histogram_duration.labels(step_name='wait_queue' ).observe(t1-t0) - histogram_duration.labels(step_name='execution' ).observe(t9-t1) - histogram_duration.labels(step_name='get_device' ).observe(t3-t2) - histogram_duration.labels(step_name='split_rules' ).observe(t5-t4) - histogram_duration.labels(step_name='configure_rules' ).observe(t6-t5) - histogram_duration.labels(step_name='deconfigure_rules').observe(t7-t6) - histogram_duration.labels(step_name='set_device' ).observe(t9-t8) + 'details', MetricTypeEnum.HISTOGRAM_DURATION) + histogram_duration.labels(step='total' , **metrics_labels).observe(t9-t0) + histogram_duration.labels(step='wait_queue' , **metrics_labels).observe(t1-t0) + histogram_duration.labels(step='execution' , **metrics_labels).observe(t9-t1) + histogram_duration.labels(step='get_device' , **metrics_labels).observe(t3-t2) + histogram_duration.labels(step='split_rules' , **metrics_labels).observe(t5-t4) + histogram_duration.labels(step='configure_rules' , **metrics_labels).observe(t6-t5) + histogram_duration.labels(step='deconfigure_rules', **metrics_labels).observe(t7-t6) + histogram_duration.labels(step='set_device' , **metrics_labels).observe(t9-t8) return device_id finally: diff --git a/src/device/service/driver_api/_Driver.py b/src/device/service/driver_api/_Driver.py index 947bc8570a941f8f666c87647d89c315b1bd202a..7adaec79dc99f9b7c836acaec886b0d5bda97fb8 100644 --- a/src/device/service/driver_api/_Driver.py +++ b/src/device/service/driver_api/_Driver.py @@ -27,7 +27,7 @@ RESOURCE_ACL = '__acl__' class _Driver: - def __init__(self, address: str, port: int, **settings) -> None: + def __init__(self, name : str, address: str, port: int, **settings) -> None: """ Initialize Driver. Parameters: address : str @@ -37,7 +37,22 @@ class _Driver: **settings Extra settings required by the driver. """ - raise NotImplementedError() + self._name = name + self._address = address + self._port = port + self._settings = settings + + @property + def name(self): return self._name + + @property + def address(self): return self._address + + @property + def port(self): return self._port + + @property + def settings(self): return self._settings def Connect(self) -> bool: """ Connect to the Device. diff --git a/src/device/service/drivers/emulated/EmulatedDriver.py b/src/device/service/drivers/emulated/EmulatedDriver.py index 2acb288784d6da5b202f14c2534ee1a59486a20e..8f9453574a7333e599ea56158204627fcfdd3680 100644 --- a/src/device/service/drivers/emulated/EmulatedDriver.py +++ b/src/device/service/drivers/emulated/EmulatedDriver.py @@ -31,16 +31,18 @@ LOGGER = logging.getLogger(__name__) RE_GET_ENDPOINT_FROM_INTERFACE = re.compile(r'^\/interface\[([^\]]+)\].*') -METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'emulated'}) +DRIVER_NAME = 'emulated' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) class EmulatedDriver(_Driver): - def __init__(self, address : str, port : int, **settings) -> None: # pylint: disable=super-init-not-called + def __init__(self, address : str, port : int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) self.__lock = threading.Lock() self.__initial = TreeNode('.') self.__running = TreeNode('.') self.__subscriptions = TreeNode('.') - endpoints = settings.get('endpoints', []) + endpoints = self.settings.get('endpoints', []) endpoint_resources = [] for endpoint in endpoints: endpoint_resource = compose_resource_endpoint(endpoint) diff --git a/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py index 96dfd2c15f6b359e254a6d6a24dfe42a546833ce..9498dc84cc6991fd2295371842fa8508c961f1bc 100644 --- a/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py +++ b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py @@ -39,21 +39,23 @@ ALL_RESOURCE_KEYS = [ SERVICE_TYPE = 'ELINE' -METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'ietf_l2vpn'}) +DRIVER_NAME = 'ietf_l2vpn' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) class IetfL2VpnDriver(_Driver): - def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called + def __init__(self, address: str, port: int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) self.__lock = threading.Lock() self.__started = threading.Event() self.__terminate = threading.Event() - username = settings.get('username') - password = settings.get('password') - scheme = settings.get('scheme', 'http') - wim = {'wim_url': '{:s}://{:s}:{:d}'.format(scheme, address, int(port))} + username = self.settings.get('username') + password = self.settings.get('password') + scheme = self.settings.get('scheme', 'http') + wim = {'wim_url': '{:s}://{:s}:{:d}'.format(scheme, self.address, int(self.port))} wim_account = {'user': username, 'password': password} # Mapping updated dynamically with each request config = {'mapping_not_needed': False, 'service_endpoint_mapping': []} - self.dac = TfsDebugApiClient(address, int(port), scheme=scheme, username=username, password=password) + self.dac = TfsDebugApiClient(self.address, int(self.port), scheme=scheme, username=username, password=password) self.wim = WimconnectorIETFL2VPN(wim, wim_account, config=config) self.conn_info = {} # internal database emulating OSM storage provided to WIM Connectors diff --git a/src/device/service/drivers/microwave/IETFApiDriver.py b/src/device/service/drivers/microwave/IETFApiDriver.py index fad7cd0736ec35c5675461af241b2e7de2295dac..a8ef9094652378df8d1f1a55868849316b7ec95b 100644 --- a/src/device/service/drivers/microwave/IETFApiDriver.py +++ b/src/device/service/drivers/microwave/IETFApiDriver.py @@ -23,20 +23,22 @@ from .Tools import create_connectivity_service, find_key, config_getter, delete_ LOGGER = logging.getLogger(__name__) -METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'microwave'}) +DRIVER_NAME = 'microwave' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) class IETFApiDriver(_Driver): - def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called + def __init__(self, address: str, port: int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) self.__lock = threading.Lock() self.__started = threading.Event() self.__terminate = threading.Event() - username = settings.get('username') - password = settings.get('password') + username = self.settings.get('username') + password = self.settings.get('password') self.__auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None - scheme = settings.get('scheme', 'http') - self.__ietf_root = '{:s}://{:s}:{:d}'.format(scheme, address, int(port)) - self.__timeout = int(settings.get('timeout', 120)) - self.__node_ids = set(settings.get('node_ids', [])) + scheme = self.settings.get('scheme', 'http') + self.__ietf_root = '{:s}://{:s}:{:d}'.format(scheme, self.address, int(self.port)) + self.__timeout = int(self.settings.get('timeout', 120)) + self.__node_ids = set(self.settings.get('node_ids', [])) def Connect(self) -> bool: url = self.__ietf_root + '/nmswebs/restconf/data/ietf-network:networks' diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index 2399b9ac01258a21a4da6a9aa0e5bc09ea851951..ac67c4ab0d314adb3ce2af0aaffeda18e67334fc 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -235,11 +235,13 @@ def edit_config( results = [e for _ in resources] # if commit fails, set exception in each resource return results -METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'openconfig'}) +DRIVER_NAME = 'openconfig' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) class OpenConfigDriver(_Driver): - def __init__(self, address : str, port : int, **settings) -> None: # pylint: disable=super-init-not-called - self.__logger = logging.getLogger('{:s}:[{:s}:{:s}]'.format(str(__name__), str(address), str(port))) + def __init__(self, address : str, port : int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) + self.__logger = logging.getLogger('{:s}:[{:s}:{:s}]'.format(str(__name__), str(self.address), str(self.port))) self.__lock = threading.Lock() #self.__initial = TreeNode('.') #self.__running = TreeNode('.') @@ -249,11 +251,11 @@ class OpenConfigDriver(_Driver): self.__scheduler = BackgroundScheduler(daemon=True) # scheduler used to emulate sampling events self.__scheduler.configure( jobstores = {'default': MemoryJobStore()}, - executors = {'default': ThreadPoolExecutor(max_workers=1)}, + executors = {'default': ThreadPoolExecutor(max_workers=1)}, # important! 1 = avoid concurrent requests job_defaults = {'coalesce': False, 'max_instances': 3}, timezone=pytz.utc) self.__out_samples = queue.Queue() - self.__netconf_handler : NetconfSessionHandler = NetconfSessionHandler(address, port, **settings) + self.__netconf_handler = NetconfSessionHandler(self.address, self.port, **(self.settings)) self.__samples_cache = SamplesCache(self.__netconf_handler, self.__logger) def Connect(self) -> bool: diff --git a/src/device/service/drivers/p4/p4_driver.py b/src/device/service/drivers/p4/p4_driver.py index de47f49c05b0f344999382883233a12eceb43c1b..49e6ed246b79c73d736d9fb91a6d4778cb08c90d 100644 --- a/src/device/service/drivers/p4/p4_driver.py +++ b/src/device/service/drivers/p4/p4_driver.py @@ -41,7 +41,8 @@ except ImportError: LOGGER = logging.getLogger(__name__) -METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'p4'}) +DRIVER_NAME = 'p4' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) class P4Driver(_Driver): """ diff --git a/src/device/service/drivers/transport_api/TransportApiDriver.py b/src/device/service/drivers/transport_api/TransportApiDriver.py index 1991a34d0d797c48b6c2296435c0ebd0f3a8125a..98ed8e6aae613ea45519143c89e72af32f3b2620 100644 --- a/src/device/service/drivers/transport_api/TransportApiDriver.py +++ b/src/device/service/drivers/transport_api/TransportApiDriver.py @@ -23,19 +23,21 @@ from .Tools import create_connectivity_service, find_key, config_getter, delete_ LOGGER = logging.getLogger(__name__) -METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'transport_api'}) +DRIVER_NAME = 'transport_api' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) class TransportApiDriver(_Driver): - def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called + def __init__(self, address: str, port: int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) self.__lock = threading.Lock() self.__started = threading.Event() self.__terminate = threading.Event() - username = settings.get('username') - password = settings.get('password') + username = self.settings.get('username') + password = self.settings.get('password') self.__auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None - scheme = settings.get('scheme', 'http') - self.__tapi_root = '{:s}://{:s}:{:d}'.format(scheme, address, int(port)) - self.__timeout = int(settings.get('timeout', 120)) + scheme = self.settings.get('scheme', 'http') + self.__tapi_root = '{:s}://{:s}:{:d}'.format(scheme, self.address, int(self.port)) + self.__timeout = int(self.settings.get('timeout', 120)) def Connect(self) -> bool: url = self.__tapi_root + '/restconf/data/tapi-common:context' diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py index c1471a8136b0e5cd7791e019bb0bdafd2252f591..46269ff8904a0e20dbcb08202220412e64cb6283 100644 --- a/src/device/service/drivers/xr/XrDriver.py +++ b/src/device/service/drivers/xr/XrDriver.py @@ -33,21 +33,23 @@ urllib3.disable_warnings() LOGGER = logging.getLogger(__name__) -METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'xr'}) +DRIVER_NAME = 'xr' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) class XrDriver(_Driver): - def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called + def __init__(self, address: str, port: int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) self.__lock = threading.Lock() self.__started = threading.Event() self.__terminate = threading.Event() - self.__timeout = int(settings.get('timeout', 120)) - self.__cm_address = address + self.__timeout = int(self.settings.get('timeout', 120)) + self.__cm_address = self.address # Mandatory key, an exception will get thrown if missing - self.__hub_module_name = settings["hub_module_name"] + self.__hub_module_name = self.settings["hub_module_name"] tls_verify = False # Currently using self signed certificates - username = settings.get("username", "xr-user-1") - password = settings.get("password", "xr-user-1") + username = self.settings.get("username", "xr-user-1") + password = self.settings.get("password", "xr-user-1") # Options are: # disabled --> just import endpoints as usual @@ -55,7 +57,7 @@ class XrDriver(_Driver): # (a remotely-controlled transport domain might exist between them) # topology --> imports sub-devices and links connecting them. # (not supported by XR driver) - self.__import_topology = get_import_topology(settings, default=ImportTopologyEnum.DISABLED) + self.__import_topology = get_import_topology(self.settings, default=ImportTopologyEnum.DISABLED) # Options are: # asynchronous --> operation considered complete when IPM responds with suitable status code, @@ -64,12 +66,12 @@ class XrDriver(_Driver): # lifecycle --> operation is considered successfull once IPM has completed pluggaable configuration # or failed in it. This is typically unsuitable for production use # (as some optics may be transiently unreachable), but is convenient for demos and testin. - consistency_mode = ConsistencyMode.from_str(settings.get("consistency-mode", "asynchronous")) + consistency_mode = ConsistencyMode.from_str(self.settings.get("consistency-mode", "asynchronous")) - self.__cm_connection = CmConnection(address, int(port), username, password, self.__timeout, tls_verify = tls_verify, consistency_mode=consistency_mode) + self.__cm_connection = CmConnection(self.address, int(self.port), username, password, self.__timeout, tls_verify = tls_verify, consistency_mode=consistency_mode) self.__constellation = None - LOGGER.info(f"XrDriver instantiated, cm {address}:{port}, consistency mode {str(consistency_mode)}, {settings=}") + LOGGER.info(f"XrDriver instantiated, cm {self.address}:{self.port}, consistency mode {str(consistency_mode)}, {self.settings=}") def __str__(self): return f"{self.__hub_module_name}@{self.__cm_address}" diff --git a/src/load_generator/command/__main__.py b/src/load_generator/command/__main__.py index 7504eb6da6d6adea698249240abf2c4e4559297a..4fa2094e0fdc94b9665b2cfc86811e67809bcb5f 100644 --- a/src/load_generator/command/__main__.py +++ b/src/load_generator/command/__main__.py @@ -34,8 +34,14 @@ def main(): RequestType.SLICE_L2NM, RequestType.SLICE_L3NM, ], + device_regex=r'.+', + endpoint_regex=r'.+', offered_load = 50, holding_time = 10, + availability_ranges = [[0.0, 99.9999]], + capacity_gbps_ranges = [[0.1, 100.00]], + e2e_latency_ms_ranges = [[5.0, 100.00]], + max_workers = 10, dry_mode = False, # in dry mode, no request is sent to TeraFlowSDN record_to_dlt = False, # if record_to_dlt, changes in device/link/service/slice are uploaded to DLT dlt_domain_id = 'dlt-perf-eval', # domain used to uploaded entities, ignored when record_to_dlt = False diff --git a/src/load_generator/load_gen/Constants.py b/src/load_generator/load_gen/Constants.py index 9ae3cdc1216891ca4dfcf01c1bd49d27bf4ef6f6..09cdecab124a776d3f71f66554db0934eaf1bb1c 100644 --- a/src/load_generator/load_gen/Constants.py +++ b/src/load_generator/load_gen/Constants.py @@ -27,4 +27,8 @@ ENDPOINT_COMPATIBILITY = { 'PHOTONIC_MEDIA:DWDM:G_50GHZ:INPUT' : 'PHOTONIC_MEDIA:DWDM:G_50GHZ:OUTPUT', } -MAX_WORKER_THREADS = 10 \ No newline at end of file +DEFAULT_AVAILABILITY_RANGES = [[0.0, 99.9999]] +DEFAULT_CAPACITY_GBPS_RANGES = [[0.1, 100.00]] +DEFAULT_E2E_LATENCY_MS_RANGES = [[5.0, 100.00]] + +DEFAULT_MAX_WORKERS = 10 diff --git a/src/load_generator/load_gen/Parameters.py b/src/load_generator/load_gen/Parameters.py index f0de3ea1aa268c520fd214f7f621953289ac5bc9..5bb7a9b725f955a4186a21201e439f9cfaa71324 100644 --- a/src/load_generator/load_gen/Parameters.py +++ b/src/load_generator/load_gen/Parameters.py @@ -13,18 +13,41 @@ # limitations under the License. from typing import List, Optional +from load_generator.load_gen.Constants import ( + DEFAULT_AVAILABILITY_RANGES, DEFAULT_CAPACITY_GBPS_RANGES, DEFAULT_E2E_LATENCY_MS_RANGES, DEFAULT_MAX_WORKERS) +from load_generator.tools.ListScalarRange import Type_ListScalarRange + class Parameters: def __init__( - self, num_requests : int, request_types : List[str], offered_load : Optional[float] = None, - inter_arrival_time : Optional[float] = None, holding_time : Optional[float] = None, do_teardown : bool = True, - dry_mode : bool = False, record_to_dlt : bool = False, dlt_domain_id : Optional[str] = None + self, + num_requests : int, + request_types : List[str], + device_regex : Optional[str] = None, + endpoint_regex : Optional[str] = None, + offered_load : Optional[float] = None, + inter_arrival_time : Optional[float] = None, + holding_time : Optional[float] = None, + availability_ranges : Type_ListScalarRange = DEFAULT_AVAILABILITY_RANGES, + capacity_gbps_ranges : Type_ListScalarRange = DEFAULT_CAPACITY_GBPS_RANGES, + e2e_latency_ms_ranges : Type_ListScalarRange = DEFAULT_E2E_LATENCY_MS_RANGES, + max_workers : int = DEFAULT_MAX_WORKERS, + do_teardown : bool = True, + dry_mode : bool = False, + record_to_dlt : bool = False, + dlt_domain_id : Optional[str] = None ) -> None: self._num_requests = num_requests self._request_types = request_types + self._device_regex = r'.*' if (device_regex is None or len(device_regex) == 0) else device_regex + self._endpoint_regex = r'.*' if (endpoint_regex is None or len(endpoint_regex) == 0) else endpoint_regex self._offered_load = offered_load self._inter_arrival_time = inter_arrival_time self._holding_time = holding_time + self._availability_ranges = availability_ranges + self._capacity_gbps_ranges = capacity_gbps_ranges + self._e2e_latency_ms_ranges = e2e_latency_ms_ranges + self._max_workers = max_workers self._do_teardown = do_teardown self._dry_mode = dry_mode self._record_to_dlt = record_to_dlt @@ -50,6 +73,12 @@ class Parameters: @property def request_types(self): return self._request_types + @property + def device_regex(self): return self._device_regex + + @property + def endpoint_regex(self): return self._endpoint_regex + @property def offered_load(self): return self._offered_load @@ -59,6 +88,18 @@ class Parameters: @property def holding_time(self): return self._holding_time + @property + def availability_ranges(self): return self._availability_ranges + + @property + def capacity_gbps_ranges(self): return self._capacity_gbps_ranges + + @property + def e2e_latency_ms_ranges(self): return self._e2e_latency_ms_ranges + + @property + def max_workers(self): return self._max_workers + @property def do_teardown(self): return self._do_teardown diff --git a/src/load_generator/load_gen/RequestGenerator.py b/src/load_generator/load_gen/RequestGenerator.py index cf56e221db0fbbe3f080d01af45cd36fc4ef56a0..974ce6f130e9c81f273f418b0a1440d148fcfb74 100644 --- a/src/load_generator/load_gen/RequestGenerator.py +++ b/src/load_generator/load_gen/RequestGenerator.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, json, random, re, threading +import logging, json, random, re, threading, uuid from typing import Dict, Optional, Set, Tuple from common.proto.context_pb2 import Empty, IsolationLevelEnum, TopologyId from common.tools.grpc.Tools import grpc_message_to_json @@ -28,6 +28,7 @@ from common.tools.object_factory.Slice import json_slice from common.tools.object_factory.Topology import json_topology_id from context.client.ContextClient import ContextClient from dlt.connector.client.DltConnectorClient import DltConnectorClient +from load_generator.tools.ListScalarRange import generate_value from .Constants import ENDPOINT_COMPATIBILITY, RequestType from .DltTools import record_device_to_dlt, record_link_to_dlt from .Parameters import Parameters @@ -53,6 +54,7 @@ class RequestGenerator: self._parameters = parameters self._lock = threading.Lock() self._num_generated = 0 + self._num_released = 0 self._available_device_endpoints : Dict[str, Set[str]] = dict() self._used_device_endpoints : Dict[str, Dict[str, str]] = dict() self._endpoint_ids_to_types : Dict[Tuple[str, str], str] = dict() @@ -64,6 +66,9 @@ class RequestGenerator: @property def num_generated(self): return self._num_generated + @property + def num_released(self): return self._num_released + @property def infinite_loop(self): return self._parameters.num_requests == 0 @@ -78,13 +83,21 @@ class RequestGenerator: if self._parameters.record_to_dlt: dlt_domain_id = TopologyId(**json_topology_id('dlt-perf-eval')) + re_device = re.compile(r'^{:s}$'.format(self._parameters.device_regex)) + re_endpoint = re.compile(r'^{:s}$'.format(self._parameters.endpoint_regex)) + devices = context_client.ListDevices(Empty()) for device in devices.devices: + if self._parameters.record_to_dlt: + record_device_to_dlt(dlt_connector_client, dlt_domain_id, device.device_id) + + if re_device.match(device.name) is None: continue device_uuid = device.device_id.device_uuid.uuid self._device_data[device_uuid] = grpc_message_to_json(device) _endpoints = self._available_device_endpoints.setdefault(device_uuid, set()) for endpoint in device.device_endpoints: + if re_endpoint.match(endpoint.name) is None: continue endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid endpoints = self._device_endpoint_data.setdefault(device_uuid, dict()) endpoints[endpoint_uuid] = grpc_message_to_json(endpoint) @@ -93,12 +106,12 @@ class RequestGenerator: _endpoints.add(endpoint_uuid) self._endpoint_ids_to_types.setdefault((device_uuid, endpoint_uuid), endpoint_type) self._endpoint_types_to_ids.setdefault(endpoint_type, set()).add((device_uuid, endpoint_uuid)) - - if self._parameters.record_to_dlt: - record_device_to_dlt(dlt_connector_client, dlt_domain_id, device.device_id) links = context_client.ListLinks(Empty()) for link in links.links: + if self._parameters.record_to_dlt: + record_link_to_dlt(dlt_connector_client, dlt_domain_id, link.link_id) + for endpoint_id in link.link_endpoint_ids: device_uuid = endpoint_id.device_id.device_uuid.uuid endpoint_uuid = endpoint_id.endpoint_uuid.uuid @@ -114,9 +127,6 @@ class RequestGenerator: endpoint_key = (device_uuid, endpoint_uuid) if endpoint_key not in endpoints_for_type: continue endpoints_for_type.discard(endpoint_key) - - if self._parameters.record_to_dlt: - record_link_to_dlt(dlt_connector_client, dlt_domain_id, link.link_id) def dump_state(self) -> None: with self._lock: @@ -186,28 +196,23 @@ class RequestGenerator: self._used_device_endpoints.setdefault(device_uuid, dict()).pop(endpoint_uuid, None) self._available_device_endpoints.setdefault(device_uuid, set()).add(endpoint_uuid) - def compose_request(self) -> Tuple[bool, Optional[Dict]]: # completed, request + def compose_request(self) -> Tuple[bool, Optional[Dict], str]: # completed, request with self._lock: if not self.infinite_loop and (self._num_generated >= self._parameters.num_requests): LOGGER.info('Generation Done!') - return True, None # completed - self._num_generated += 1 - num_request = self._num_generated + return True, None, None # completed - #request_uuid = str(uuid.uuid4()) - request_uuid = 'svc_{:d}'.format(num_request) - - # choose request type + request_uuid = str(uuid.uuid4()) request_type = random.choice(self._parameters.request_types) if request_type in { RequestType.SERVICE_L2NM, RequestType.SERVICE_L3NM, RequestType.SERVICE_TAPI, RequestType.SERVICE_MW }: - return False, self._compose_service(num_request, request_uuid, request_type) + return False, self._compose_service(request_uuid, request_type), request_type elif request_type in {RequestType.SLICE_L2NM, RequestType.SLICE_L3NM}: - return False, self._compose_slice(num_request, request_uuid, request_type) + return False, self._compose_slice(request_uuid, request_type), request_type - def _compose_service(self, num_request : int, request_uuid : str, request_type : str) -> Optional[Dict]: + def _compose_service(self, request_uuid : str, request_type : str) -> Optional[Dict]: # choose source endpoint src_endpoint_types = set(ENDPOINT_COMPATIBILITY.keys()) if request_type in {RequestType.SERVICE_TAPI} else None src = self._use_device_endpoint(request_uuid, request_type, endpoint_types=src_endpoint_types) @@ -236,6 +241,10 @@ class RequestGenerator: self._release_device_endpoint(src_device_uuid, src_endpoint_uuid) return None + with self._lock: + self._num_generated += 1 + num_request = self._num_generated + # compose endpoints dst_device_uuid,dst_endpoint_uuid = dst endpoint_ids = [ @@ -244,9 +253,9 @@ class RequestGenerator: ] if request_type == RequestType.SERVICE_L2NM: - availability = round(random.uniform(0.0, 99.9999), ndigits=5) - capacity_gbps = round(random.uniform(0.1, 100.00), ndigits=2) - e2e_latency_ms = round(random.uniform(5.0, 100.00), ndigits=2) + availability = generate_value(self._parameters.availability_ranges, ndigits=5) + capacity_gbps = generate_value(self._parameters.capacity_gbps_ranges, ndigits=2) + e2e_latency_ms = generate_value(self._parameters.e2e_latency_ms_ranges, ndigits=2) constraints = [ json_constraint_sla_availability(1, True, availability), @@ -293,9 +302,9 @@ class RequestGenerator: request_uuid, endpoint_ids=endpoint_ids, constraints=constraints, config_rules=config_rules) elif request_type == RequestType.SERVICE_L3NM: - availability = round(random.uniform(0.0, 99.9999), ndigits=5) - capacity_gbps = round(random.uniform(0.1, 100.00), ndigits=2) - e2e_latency_ms = round(random.uniform(5.0, 100.00), ndigits=2) + availability = generate_value(self._parameters.availability_ranges, ndigits=5) + capacity_gbps = generate_value(self._parameters.capacity_gbps_ranges, ndigits=2) + e2e_latency_ms = generate_value(self._parameters.e2e_latency_ms_ranges, ndigits=2) constraints = [ json_constraint_sla_availability(1, True, availability), @@ -382,7 +391,7 @@ class RequestGenerator: return json_service_l2nm_planned( request_uuid, endpoint_ids=endpoint_ids, constraints=[], config_rules=config_rules) - def _compose_slice(self, num_request : int, request_uuid : str, request_type : str) -> Optional[Dict]: + def _compose_slice(self, request_uuid : str, request_type : str) -> Optional[Dict]: # choose source endpoint src = self._use_device_endpoint(request_uuid, request_type) if src is None: @@ -403,6 +412,10 @@ class RequestGenerator: self._release_device_endpoint(src_device_uuid, src_endpoint_uuid) return None + with self._lock: + self._num_generated += 1 + num_request = self._num_generated + # compose endpoints dst_device_uuid,dst_endpoint_uuid = dst endpoint_ids = [ @@ -410,9 +423,10 @@ class RequestGenerator: json_endpoint_id(json_device_id(dst_device_uuid), dst_endpoint_uuid), ] - availability = round(random.uniform(0.0, 99.9999), ndigits=5) - capacity_gbps = round(random.uniform(0.1, 100.00), ndigits=2) - e2e_latency_ms = round(random.uniform(5.0, 100.00), ndigits=2) + availability = generate_value(self._parameters.availability_ranges, ndigits=5) + capacity_gbps = generate_value(self._parameters.capacity_gbps_ranges, ndigits=2) + e2e_latency_ms = generate_value(self._parameters.e2e_latency_ms_ranges, ndigits=2) + constraints = [ json_constraint_sla_availability(1, True, availability), json_constraint_sla_capacity(capacity_gbps), @@ -503,8 +517,15 @@ class RequestGenerator: device_uuid = endpoint_id['device_id']['device_uuid']['uuid'] endpoint_uuid = endpoint_id['endpoint_uuid']['uuid'] self._release_device_endpoint(device_uuid, endpoint_uuid) + + with self._lock: + self._num_released += 1 + elif 'slice_id' in json_request: for endpoint_id in json_request['slice_endpoint_ids']: device_uuid = endpoint_id['device_id']['device_uuid']['uuid'] endpoint_uuid = endpoint_id['endpoint_uuid']['uuid'] self._release_device_endpoint(device_uuid, endpoint_uuid) + + with self._lock: + self._num_released += 1 diff --git a/src/load_generator/load_gen/RequestScheduler.py b/src/load_generator/load_gen/RequestScheduler.py index 773a37eac258f8b3c16c966464ced124d3c77c85..340a5411bacee7b0aeb495963cdf65fbb5d14389 100644 --- a/src/load_generator/load_gen/RequestScheduler.py +++ b/src/load_generator/load_gen/RequestScheduler.py @@ -18,10 +18,11 @@ from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.schedulers.blocking import BlockingScheduler from datetime import datetime, timedelta from typing import Dict, Optional +from common.method_wrappers.Decorator import MetricsPool from common.proto.context_pb2 import Service, ServiceId, Slice, SliceId +from common.tools.grpc.Tools import grpc_message_to_json_string from service.client.ServiceClient import ServiceClient from slice.client.SliceClient import SliceClient -from .Constants import MAX_WORKER_THREADS from .DltTools import explore_entities_to_record, record_entities from .Parameters import Parameters from .RequestGenerator import RequestGenerator @@ -31,6 +32,10 @@ logging.getLogger('apscheduler.scheduler').setLevel(logging.WARNING) LOGGER = logging.getLogger(__name__) +METRICS_POOL = MetricsPool('LoadGen', 'Requests', labels={ + 'request_type': '' +}) + class RequestScheduler: def __init__( self, parameters : Parameters, generator : RequestGenerator, scheduler_class=BlockingScheduler @@ -38,7 +43,7 @@ class RequestScheduler: self._scheduler = scheduler_class() self._scheduler.configure( jobstores = {'default': MemoryJobStore()}, - executors = {'default': ThreadPoolExecutor(max_workers=MAX_WORKER_THREADS)}, + executors = {'default': ThreadPoolExecutor(max_workers=parameters.max_workers)}, job_defaults = { 'coalesce': False, 'max_instances': 100, @@ -52,6 +57,9 @@ class RequestScheduler: @property def num_generated(self): return min(self._generator.num_generated, self._parameters.num_requests) + @property + def num_released(self): return min(self._generator.num_released, self._parameters.num_requests) + @property def infinite_loop(self): return self._generator.infinite_loop @@ -64,11 +72,12 @@ class RequestScheduler: self._scheduler.add_job( self._request_setup, trigger='date', run_date=run_date, timezone=pytz.utc) - def _schedule_request_teardown(self, request : Dict) -> None: + def _schedule_request_teardown(self, request : Dict, request_type : str) -> None: ht = random.expovariate(1.0 / self._parameters.holding_time) run_date = datetime.utcnow() + timedelta(seconds=ht) + args = (request, request_type) self._scheduler.add_job( - self._request_teardown, args=(request,), trigger='date', run_date=run_date, timezone=pytz.utc) + self._request_teardown, args=args, trigger='date', run_date=run_date, timezone=pytz.utc) def start(self): self._running.set() @@ -80,7 +89,7 @@ class RequestScheduler: self._running.clear() def _request_setup(self) -> None: - completed,request = self._generator.compose_request() + completed, request, request_type = self._generator.compose_request() if completed: LOGGER.info('Generation Done!') #self._scheduler.shutdown() @@ -91,6 +100,9 @@ class RequestScheduler: if request is None: LOGGER.warning('No resources available to compose new request') + metrics = METRICS_POOL.get_metrics_loadgen('setup', labels={'request_type': request_type}) + _, _, _, _, counter_blocked = metrics + counter_blocked.inc() return if 'service_id' in request: @@ -101,7 +113,7 @@ class RequestScheduler: dst_endpoint_uuid = request['service_endpoint_ids'][1]['endpoint_uuid']['uuid'] LOGGER.info('Setup Service: uuid=%s src=%s:%s dst=%s:%s', service_uuid, src_device_uuid, src_endpoint_uuid, dst_device_uuid, dst_endpoint_uuid) - self._create_update(service=request) + self._create_update(request_type, service=request) elif 'slice_id' in request: slice_uuid = request['slice_id']['slice_uuid']['uuid'] @@ -111,12 +123,12 @@ class RequestScheduler: dst_endpoint_uuid = request['slice_endpoint_ids'][1]['endpoint_uuid']['uuid'] LOGGER.info('Setup Slice: uuid=%s src=%s:%s dst=%s:%s', slice_uuid, src_device_uuid, src_endpoint_uuid, dst_device_uuid, dst_endpoint_uuid) - self._create_update(slice_=request) + self._create_update(request_type, slice_=request) if self._parameters.do_teardown: - self._schedule_request_teardown(request) + self._schedule_request_teardown(request, request_type) - def _request_teardown(self, request : Dict) -> None: + def _request_teardown(self, request : Dict, request_type : str) -> None: if 'service_id' in request: service_uuid = request['service_id']['service_uuid']['uuid'] src_device_uuid = request['service_endpoint_ids'][0]['device_id']['device_uuid']['uuid'] @@ -125,7 +137,7 @@ class RequestScheduler: dst_endpoint_uuid = request['service_endpoint_ids'][1]['endpoint_uuid']['uuid'] LOGGER.info('Teardown Service: uuid=%s src=%s:%s dst=%s:%s', service_uuid, src_device_uuid, src_endpoint_uuid, dst_device_uuid, dst_endpoint_uuid) - self._delete(service_id=ServiceId(**(request['service_id']))) + self._delete(request_type, service_id=ServiceId(**(request['service_id']))) elif 'slice_id' in request: slice_uuid = request['slice_id']['slice_uuid']['uuid'] @@ -135,33 +147,64 @@ class RequestScheduler: dst_endpoint_uuid = request['slice_endpoint_ids'][1]['endpoint_uuid']['uuid'] LOGGER.info('Teardown Slice: uuid=%s src=%s:%s dst=%s:%s', slice_uuid, src_device_uuid, src_endpoint_uuid, dst_device_uuid, dst_endpoint_uuid) - self._delete(slice_id=SliceId(**(request['slice_id']))) + self._delete(request_type, slice_id=SliceId(**(request['slice_id']))) self._generator.release_request(request) - def _create_update(self, service : Optional[Dict] = None, slice_ : Optional[Dict] = None) -> None: + def _create_update( + self, request_type : str, service : Optional[Dict] = None, slice_ : Optional[Dict] = None + ) -> None: if self._parameters.dry_mode: return + metrics = METRICS_POOL.get_metrics_loadgen('setup', labels={'request_type': request_type}) + histogram_duration, counter_started, counter_completed, counter_failed, _ = metrics + service_id = None if service is not None: + service_client = ServiceClient() + service_add = copy.deepcopy(service) service_add['service_endpoint_ids'] = [] service_add['service_constraints'] = [] service_add['service_config'] = {'config_rules': []} + service_add = Service(**service_add) + service = Service(**service) + + with histogram_duration.time(): + try: + counter_started.inc() + service_id = service_client.CreateService(service_add) + service_id = service_client.UpdateService(service) + counter_completed.inc() + except: # pylint: disable=bare-except + counter_failed.inc() + MSG = 'Exception Setting Up Service {:s}' + LOGGER.exception(MSG.format(grpc_message_to_json_string(service))) - service_client = ServiceClient() - service_id = service_client.CreateService(Service(**service_add)) service_client.close() slice_id = None if slice_ is not None: + slice_client = SliceClient() + slice_add = copy.deepcopy(slice_) slice_add['slice_endpoint_ids'] = [] slice_add['slice_constraints'] = [] slice_add['slice_config'] = {'config_rules': []} + slice_add = Slice(**slice_add) + slice_ = Slice(**slice_) + + with histogram_duration.time(): + try: + counter_started.inc() + slice_id = slice_client.CreateSlice(slice_add) + slice_id = slice_client.UpdateSlice(slice_) + counter_completed.inc() + except: # pylint: disable=bare-except + counter_failed.inc() + MSG = 'Exception Setting Up Slice {:s}' + LOGGER.exception(MSG.format(grpc_message_to_json_string(slice_))) - slice_client = SliceClient() - slice_id = slice_client.CreateSlice(Slice(**slice_add)) slice_client.close() if self._parameters.record_to_dlt: @@ -171,41 +214,47 @@ class RequestScheduler: slices_to_record=slices_to_record, services_to_record=services_to_record, devices_to_record=devices_to_record, delete=False) - service_id = None - if service is not None: - service_client = ServiceClient() - service_id = service_client.UpdateService(Service(**service)) - service_client.close() + def _delete( + self, request_type : str, service_id : Optional[ServiceId] = None, slice_id : Optional[SliceId] = None + ) -> None: + if self._parameters.dry_mode: return - slice_id = None - if slice_ is not None: - slice_client = SliceClient() - slice_id = slice_client.UpdateSlice(Slice(**slice_)) - slice_client.close() + metrics = METRICS_POOL.get_metrics_loadgen('teardown', labels={'request_type': request_type}) + histogram_duration, counter_started, counter_completed, counter_failed, _ = metrics if self._parameters.record_to_dlt: entities_to_record = explore_entities_to_record(slice_id=slice_id, service_id=service_id) slices_to_record, services_to_record, devices_to_record = entities_to_record - record_entities( - slices_to_record=slices_to_record, services_to_record=services_to_record, - devices_to_record=devices_to_record, delete=False) - def _delete(self, service_id : Optional[ServiceId] = None, slice_id : Optional[SliceId] = None) -> None: - if self._parameters.dry_mode: return + if service_id is not None: + service_client = ServiceClient() - if self._parameters.record_to_dlt: - entities_to_record = explore_entities_to_record(slice_id=slice_id, service_id=service_id) - slices_to_record, services_to_record, devices_to_record = entities_to_record + with histogram_duration.time(): + try: + counter_started.inc() + service_client.DeleteService(service_id) + counter_completed.inc() + except: # pylint: disable=bare-except + counter_failed.inc() + MSG = 'Exception Tearing Down Service {:s}' + LOGGER.exception(MSG.format(grpc_message_to_json_string(service_id))) + + service_client.close() if slice_id is not None: slice_client = SliceClient() - slice_client.DeleteSlice(slice_id) - slice_client.close() - if service_id is not None: - service_client = ServiceClient() - service_client.DeleteService(service_id) - service_client.close() + with histogram_duration.time(): + try: + counter_started.inc() + slice_client.DeleteSlice(slice_id) + counter_completed.inc() + except: # pylint: disable=bare-except + counter_failed.inc() + MSG = 'Exception Tearing Down Slice {:s}' + LOGGER.exception(MSG.format(grpc_message_to_json_string(slice_id))) + + slice_client.close() if self._parameters.record_to_dlt: record_entities( diff --git a/src/load_generator/service/LoadGeneratorServiceServicerImpl.py b/src/load_generator/service/LoadGeneratorServiceServicerImpl.py index d66b0b2c10c5228e0c3d15759fc46b2c0770154d..866f9f089662598b08c8dd03d04b01fd63108f5a 100644 --- a/src/load_generator/service/LoadGeneratorServiceServicerImpl.py +++ b/src/load_generator/service/LoadGeneratorServiceServicerImpl.py @@ -21,6 +21,7 @@ from common.proto.load_generator_pb2_grpc import LoadGeneratorServiceServicer from load_generator.load_gen.Parameters import Parameters as LoadGen_Parameters from load_generator.load_gen.RequestGenerator import RequestGenerator from load_generator.load_gen.RequestScheduler import RequestScheduler +from load_generator.tools.ListScalarRange import list_scalar_range__grpc_to_list, list_scalar_range__list_to_grpc from .Constants import REQUEST_TYPE_MAP, REQUEST_TYPE_REVERSE_MAP LOGGER = logging.getLogger(__name__) @@ -34,15 +35,21 @@ class LoadGeneratorServiceServicerImpl(LoadGeneratorServiceServicer): def Start(self, request : Parameters, context : grpc.ServicerContext) -> Empty: self._parameters = LoadGen_Parameters( - num_requests = request.num_requests, - request_types = [REQUEST_TYPE_MAP[rt] for rt in request.request_types], - offered_load = request.offered_load if request.offered_load > 1.e-12 else None, - holding_time = request.holding_time if request.holding_time > 1.e-12 else None, - inter_arrival_time = request.inter_arrival_time if request.inter_arrival_time > 1.e-12 else None, - do_teardown = request.do_teardown, # if set, schedule tear down of requests - dry_mode = request.dry_mode, # in dry mode, no request is sent to TeraFlowSDN - record_to_dlt = request.record_to_dlt, # if set, upload changes to DLT - dlt_domain_id = request.dlt_domain_id, # domain used to uploaded entities (when record_to_dlt = True) + num_requests = request.num_requests, + request_types = [REQUEST_TYPE_MAP[rt] for rt in request.request_types], + device_regex = request.device_regex, + endpoint_regex = request.endpoint_regex, + offered_load = request.offered_load if request.offered_load > 1.e-12 else None, + holding_time = request.holding_time if request.holding_time > 1.e-12 else None, + inter_arrival_time = request.inter_arrival_time if request.inter_arrival_time > 1.e-12 else None, + availability_ranges = list_scalar_range__grpc_to_list(request.availability ), + capacity_gbps_ranges = list_scalar_range__grpc_to_list(request.capacity_gbps ), + e2e_latency_ms_ranges = list_scalar_range__grpc_to_list(request.e2e_latency_ms), + max_workers = request.max_workers, + do_teardown = request.do_teardown, # if set, schedule tear down of requests + dry_mode = request.dry_mode, # in dry mode, no request is sent to TeraFlowSDN + record_to_dlt = request.record_to_dlt, # if set, upload changes to DLT + dlt_domain_id = request.dlt_domain_id, # domain used to uploaded entities (when record_to_dlt = True) ) LOGGER.info('Initializing Generator...') @@ -68,17 +75,28 @@ class LoadGeneratorServiceServicerImpl(LoadGeneratorServiceServicer): status = Status() status.num_generated = self._scheduler.num_generated + status.num_released = self._scheduler.num_released status.infinite_loop = self._scheduler.infinite_loop status.running = self._scheduler.running - status.parameters.num_requests = params.num_requests # pylint: disable=no-member - status.parameters.offered_load = params.offered_load # pylint: disable=no-member - status.parameters.holding_time = params.holding_time # pylint: disable=no-member - status.parameters.inter_arrival_time = params.inter_arrival_time # pylint: disable=no-member - status.parameters.do_teardown = params.do_teardown # pylint: disable=no-member - status.parameters.dry_mode = params.dry_mode # pylint: disable=no-member - status.parameters.record_to_dlt = params.record_to_dlt # pylint: disable=no-member - status.parameters.dlt_domain_id = params.dlt_domain_id # pylint: disable=no-member - status.parameters.request_types.extend(request_types) # pylint: disable=no-member + + stat_pars = status.parameters # pylint: disable=no-member + stat_pars.num_requests = params.num_requests # pylint: disable=no-member + stat_pars.device_regex = params.device_regex # pylint: disable=no-member + stat_pars.endpoint_regex = params.endpoint_regex # pylint: disable=no-member + stat_pars.offered_load = params.offered_load # pylint: disable=no-member + stat_pars.holding_time = params.holding_time # pylint: disable=no-member + stat_pars.inter_arrival_time = params.inter_arrival_time # pylint: disable=no-member + stat_pars.max_workers = params.max_workers # pylint: disable=no-member + stat_pars.do_teardown = params.do_teardown # pylint: disable=no-member + stat_pars.dry_mode = params.dry_mode # pylint: disable=no-member + stat_pars.record_to_dlt = params.record_to_dlt # pylint: disable=no-member + stat_pars.dlt_domain_id = params.dlt_domain_id # pylint: disable=no-member + stat_pars.request_types.extend(request_types) # pylint: disable=no-member + + list_scalar_range__list_to_grpc(params.availability_ranges, stat_pars.availability ) # pylint: disable=no-member + list_scalar_range__list_to_grpc(params.capacity_gbps_ranges, stat_pars.capacity_gbps ) # pylint: disable=no-member + list_scalar_range__list_to_grpc(params.e2e_latency_ms_ranges, stat_pars.e2e_latency_ms) # pylint: disable=no-member + return status def Stop(self, request : Empty, context : grpc.ServicerContext) -> Empty: diff --git a/src/load_generator/service/__main__.py b/src/load_generator/service/__main__.py index 227099c59aa57f420c842a6210f3b8b146b23cda..7051a9a18bb2a86e2ca298b9ddfdc32f3e3fa6e7 100644 --- a/src/load_generator/service/__main__.py +++ b/src/load_generator/service/__main__.py @@ -13,14 +13,15 @@ # limitations under the License. import logging, signal, sys, threading +from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( - ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, wait_for_environment_variables) from .LoadGeneratorService import LoadGeneratorService -log_level = get_log_level() -logging.basicConfig(level=log_level, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") +LOG_LEVEL = get_log_level() +logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") LOGGER = logging.getLogger(__name__) terminate = threading.Event() @@ -39,10 +40,13 @@ def main(): get_env_var_name(ServiceNameEnum.SLICE, ENVVAR_SUFIX_SERVICE_PORT_GRPC), ]) + LOGGER.info('Starting...') signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - LOGGER.info('Starting...') + # Start metrics server + metrics_port = get_metrics_port() + start_http_server(metrics_port) # Starting load generator service grpc_service = LoadGeneratorService() diff --git a/src/load_generator/tools/ListScalarRange.py b/src/load_generator/tools/ListScalarRange.py new file mode 100644 index 0000000000000000000000000000000000000000..9a5a5f39940049adee6dfbe35befd815db9256fe --- /dev/null +++ b/src/load_generator/tools/ListScalarRange.py @@ -0,0 +1,99 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# 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. + +import random +from typing import List, Optional, Tuple, Union + +from common.proto.load_generator_pb2 import ScalarOrRange + +# RegEx to validate strings formatted as: '1, 2.3, 4.5 .. 6.7 , .8...9, 10., .11' +# IMPORTANT: this regex just validates data, it does not extract the pieces of data! +RE_FLOAT = r'[\ ]*[0-9]*[\.]?[0-9]*[\ ]*' +RE_RANGE = RE_FLOAT + r'(\.\.' + RE_FLOAT + r')?' +RE_SCALAR_RANGE_LIST = RE_RANGE + r'(\,' + RE_RANGE + r')*' + +Type_ListScalarRange = List[Union[float, Tuple[float, float]]] + +def parse_list_scalar_range(value : str) -> Type_ListScalarRange: + str_value = str(value).replace(' ', '') + ranges = [[float(value) for value in item.split('..')] for item in str_value.split(',')] + return ranges + +def list_scalar_range__list_to_grpc(list_scalar_range : Type_ListScalarRange, obj : List[ScalarOrRange]) -> None: + for i,scalar_or_range in enumerate(list_scalar_range): + if isinstance(scalar_or_range, (float, str)): + _scalar = obj.add() + _scalar.scalar = float(scalar_or_range) + elif isinstance(scalar_or_range, (list, tuple)): + if len(scalar_or_range) == 1: + _scalar = obj.add() + _scalar.scalar = float(scalar_or_range[0]) + elif len(scalar_or_range) == 2: + _range = obj.add() + _range.range.minimum = float(scalar_or_range[0]) + _range.range.maximum = float(scalar_or_range[1]) + else: + MSG = 'List/tuple with {:d} items in item(#{:d}, {:s})' + raise NotImplementedError(MSG.format(len(scalar_or_range), i, str(scalar_or_range))) + else: + MSG = 'Type({:s}) in item(#{:d}, {:s})' + raise NotImplementedError(MSG.format(str(type(scalar_or_range), i, str(scalar_or_range)))) + +def list_scalar_range__grpc_to_str(obj : List[ScalarOrRange]) -> str: + str_items = list() + for item in obj: + item_kind = item.WhichOneof('value') + if item_kind == 'scalar': + str_items.append(str(item.scalar)) + elif item_kind == 'range': + str_items.append('{:s}..{:s}'.format(str(item.range.minimum), str(item.range.maximum))) + else: + raise NotImplementedError('Unsupported ScalarOrRange kind({:s})'.format(str(item_kind))) + return ','.join(str_items) + +def list_scalar_range__grpc_to_list(obj : List[ScalarOrRange]) -> Type_ListScalarRange: + list_scalar_range = list() + for item in obj: + item_kind = item.WhichOneof('value') + if item_kind == 'scalar': + scalar_or_range = float(item.scalar) + elif item_kind == 'range': + scalar_or_range = (float(item.range.minimum), float(item.range.maximum)) + else: + raise NotImplementedError('Unsupported ScalarOrRange kind({:s})'.format(str(item_kind))) + list_scalar_range.append(scalar_or_range) + return list_scalar_range + +def generate_value( + list_scalar_range : Type_ListScalarRange, ndigits : Optional[int] = None +) -> float: + scalar_or_range = random.choice(list_scalar_range) + if isinstance(scalar_or_range, (float, str)): + value = float(scalar_or_range) + elif isinstance(scalar_or_range, (list, tuple)): + if len(scalar_or_range) == 1: + value = float(scalar_or_range[0]) + elif len(scalar_or_range) == 2: + minimum = float(scalar_or_range[0]) + maximum = float(scalar_or_range[1]) + value = random.uniform(minimum, maximum) + else: + MSG = 'List/tuple with {:d} items in item({:s})' + raise NotImplementedError(MSG.format(len(scalar_or_range), str(scalar_or_range))) + else: + MSG = 'Type({:s}) in item({:s})' + raise NotImplementedError(MSG.format(str(type(scalar_or_range), str(scalar_or_range)))) + + if ndigits is None: return value + return round(value, ndigits=ndigits) diff --git a/src/load_generator/tools/__init__.py b/src/load_generator/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..38d04994fb0fa1951fb465bc127eb72659dc2eaf --- /dev/null +++ b/src/load_generator/tools/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# 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. diff --git a/src/pathcomp/backend/pathComp_tools.h b/src/pathcomp/backend/pathComp_tools.h index cac66f81c561502a6d93249f5e44a6195cb0f61b..84334eb5e1d47199e8a71bb09c3b541625d66af2 100644 --- a/src/pathcomp/backend/pathComp_tools.h +++ b/src/pathcomp/backend/pathComp_tools.h @@ -124,7 +124,7 @@ struct map_nodes_t { gint numMapNodes; }; -#define MAX_NUM_VERTICES 20 // 100 # LGR: reduced from 100 to 20 to divide by 5 the memory used +#define MAX_NUM_VERTICES 100 // 100 # LGR: reduced from 100 to 20 to divide by 5 the memory used #define MAX_NUM_EDGES 5 // 100 # LGR: reduced from 100 to 5 to divide by 20 the memory used // Structures for the graph composition struct targetNodes_t { @@ -249,7 +249,7 @@ struct endPoint_t { // Structure for the device contents /////////////////////////////////////////////////////////////////// #define MAX_DEV_TYPE_SIZE 128 -#define MAX_DEV_ENDPOINT_LENGTH 50 // 10 # LGR: controllers might have large number of endpoints +#define MAX_DEV_ENDPOINT_LENGTH 100 // 10 # LGR: controllers might have large number of endpoints struct device_t { gdouble power_idle; // power idle (baseline) of the switch in Watts gint operational_status; // 0 - Undefined, 1 - Disabled, 2 - Enabled diff --git a/src/webui/Dockerfile b/src/webui/Dockerfile index 7c718890fcf3f07b32f66eca2ecab41f2eb30fbb..2a1510954dbd2a9b0817f94145baaa22ac9d3a3f 100644 --- a/src/webui/Dockerfile +++ b/src/webui/Dockerfile @@ -79,6 +79,7 @@ COPY --chown=webui:webui src/device/__init__.py device/__init__.py COPY --chown=webui:webui src/device/client/. device/client/ COPY --chown=webui:webui src/load_generator/__init__.py load_generator/__init__.py COPY --chown=webui:webui src/load_generator/client/. load_generator/client/ +COPY --chown=webui:webui src/load_generator/tools/. load_generator/tools/ COPY --chown=webui:webui src/service/__init__.py service/__init__.py COPY --chown=webui:webui src/service/client/. service/client/ COPY --chown=webui:webui src/slice/__init__.py slice/__init__.py diff --git a/src/webui/grafana_prom_component_rpc.json b/src/webui/grafana_prom_component_rpc.json new file mode 100644 index 0000000000000000000000000000000000000000..ce40c2854df2f71fe07601ca4fada945cab22fa6 --- /dev/null +++ b/src/webui/grafana_prom_component_rpc.json @@ -0,0 +1,427 @@ +{"overwrite": true, "folderId": 0, "dashboard": + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1671297223428, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tfs_[[component]]_rpc_[[method]]_counter_requests_started_total{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "started", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum(tfs_[[component]]_rpc_[[method]]_counter_requests_completed_total{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "completed", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(tfs_[[component]]_rpc_[[method]]_counter_requests_failed_total{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "failed", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:935", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:936", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "linear", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "max": null, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 2, + "interval": "60s", + "legend": { + "show": true + }, + "pluginVersion": "7.5.4", + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(\r\n max_over_time(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket{pod=~\"[[pod]]\"}[1m]) -\r\n min_over_time(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket{pod=~\"[[pod]]\"}[1m])\r\n) by (le)", + "format": "heatmap", + "instant": false, + "interval": "1m", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "title": "Histogram", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "s", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tfs_[[component]]_rpc_[[method]]_histogram_duration_sum{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "total time", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total Exec Time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:407", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:408", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": false, + "text": "context", + "value": "context" + }, + "datasource": "prometheus", + "definition": "metrics(tfs_)", + "description": null, + "error": null, + "hide": 0, + "includeAll": false, + "label": "Component", + "multi": false, + "name": "component", + "options": [], + "query": { + "query": "metrics(tfs_)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "/tfs_(.+)_rpc_.*/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "", + "current": { + "selected": false, + "text": "getcontext", + "value": "getcontext" + }, + "datasource": "prometheus", + "definition": "metrics(tfs_[[component]]_rpc_)", + "description": null, + "error": null, + "hide": 0, + "includeAll": false, + "label": "Method", + "multi": false, + "name": "method", + "options": [], + "query": { + "query": "metrics(tfs_[[component]]_rpc_)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "/tfs_[[component]]_rpc_(.+)_histogram_duration_bucket/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "prometheus", + "definition": "label_values(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket, pod)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Pod", + "multi": true, + "name": "pod", + "options": [], + "query": { + "query": "label_values(tfs_[[component]]_rpc_[[method]]_histogram_duration_bucket, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "TFS / Component RPCs", + "uid": "tfs-comp-rpc", + "version": 21 + } +} diff --git a/src/webui/grafana_prom_device_driver.json b/src/webui/grafana_prom_device_driver.json new file mode 100644 index 0000000000000000000000000000000000000000..af4ccca88a905b7ebb25ca0e23506b7b618011ad --- /dev/null +++ b/src/webui/grafana_prom_device_driver.json @@ -0,0 +1,432 @@ +{"overwrite": true, "folderId": 0, "dashboard": + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1671318718779, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tfs_device_driver_[[method]]_counter_requests_started_total{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "started", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum(tfs_device_driver_[[method]]_counter_requests_completed_total{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "completed", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(tfs_device_driver_[[method]]_counter_requests_failed_total{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "failed", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:864", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:865", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "linear", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "max": null, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 2, + "interval": "60s", + "legend": { + "show": true + }, + "pluginVersion": "7.5.4", + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(\r\n max_over_time(tfs_device_driver_[[method]]_histogram_duration_bucket{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"}[1m]) -\r\n min_over_time(tfs_device_driver_[[method]]_histogram_duration_bucket{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"}[1m])\r\n) by (le)", + "format": "heatmap", + "instant": false, + "interval": "60s", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "timeFrom": null, + "title": "Histogram", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "s", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tfs_device_driver_[[method]]_histogram_duration_sum{driver=~\"[[driver]]\", pod=~\"deviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "total time", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total Exec Time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:407", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:408", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": "", + "current": { + "selected": false, + "text": "setconfig", + "value": "setconfig" + }, + "datasource": "prometheus", + "definition": "metrics(tfs_device_driver_.+)", + "description": null, + "error": null, + "hide": 0, + "includeAll": false, + "label": "Method", + "multi": false, + "name": "method", + "options": [], + "query": { + "query": "metrics(tfs_device_driver_.+)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "/tfs_device_driver_(.+config)_histogram_duration_bucket/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "prometheus", + "definition": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, driver)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Driver", + "multi": true, + "name": "driver", + "options": [], + "query": { + "query": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, driver)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "prometheus", + "definition": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, pod)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Pod", + "multi": true, + "name": "pod", + "options": [], + "query": { + "query": "label_values(tfs_device_driver_[[method]]_histogram_duration_bucket, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "/deviceservice-(.*)/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "TFS / Device / Driver", + "uid": "tfs-dev-drv", + "version": 30 + } +} diff --git a/src/webui/grafana_prom_device_exec_details.json b/src/webui/grafana_prom_device_exec_details.json new file mode 100644 index 0000000000000000000000000000000000000000..a18c2b91cabd942dd5908ff3e6b3966528e62fa0 --- /dev/null +++ b/src/webui/grafana_prom_device_exec_details.json @@ -0,0 +1,258 @@ +{"overwrite": true, "folderId": 0, "dashboard": + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "iteration": 1683036452435, + "links": [], + "liveNow": false, + "panels": [ + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "linear", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 22, + "w": 24, + "x": 0, + "y": 0 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 2, + "interval": "60s", + "legend": { + "show": true + }, + "pluginVersion": "7.5.4", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(\r\n max_over_time(tfs_device_execution_details_histogram_duration_bucket{driver=~\"[[driver]]\", operation=~\"[[operation]]\", step=~\"[[step]]\"}[1m]) -\r\n min_over_time(tfs_device_execution_details_histogram_duration_bucket{driver=~\"[[driver]]\", operation=~\"[[operation]]\", step=~\"[[step]]\"}[1m])\r\n) by (le)", + "format": "heatmap", + "instant": false, + "interval": "1m", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Histogram", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + ], + "refresh": "5s", + "schemaVersion": 36, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(tfs_device_execution_details_histogram_duration_bucket, operation)", + "hide": 0, + "includeAll": true, + "label": "Operation", + "multi": true, + "name": "operation", + "options": [], + "query": { + "query": "label_values(tfs_device_execution_details_histogram_duration_bucket, operation)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(tfs_device_execution_details_histogram_duration_bucket, driver)", + "hide": 0, + "includeAll": true, + "label": "Driver", + "multi": true, + "name": "driver", + "options": [], + "query": { + "query": "label_values(tfs_device_execution_details_histogram_duration_bucket, driver)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(tfs_device_execution_details_histogram_duration_bucket, step)", + "hide": 0, + "includeAll": true, + "label": "Step", + "multi": true, + "name": "step", + "options": [], + "query": { + "query": "label_values(tfs_device_execution_details_histogram_duration_bucket, step)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(tfs_device_execution_details_histogram_duration_bucket, pod)", + "hide": 0, + "includeAll": true, + "label": "Pod", + "multi": true, + "name": "pod", + "options": [], + "query": { + "query": "label_values(tfs_device_execution_details_histogram_duration_bucket, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "TFS / Device Execution Details", + "uid": "tfs-dev-exec", + "version": 4, + "weekStart": "" + } +} diff --git a/src/webui/grafana_prom_load_generator.json b/src/webui/grafana_prom_load_generator.json new file mode 100644 index 0000000000000000000000000000000000000000..efdc8a1180e0d44f726becf5c8125c34779f7c78 --- /dev/null +++ b/src/webui/grafana_prom_load_generator.json @@ -0,0 +1,399 @@ +{"overwrite": true, "folderId": 0, "dashboard": + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "iteration": 1682528742676, + "links": [], + "liveNow": false, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.5.22", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "exemplar": true, + "expr": "sum(tfs_loadgen_requests_[[method]]_counter_requests_started_total{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "started", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "exemplar": true, + "expr": "sum(tfs_loadgen_requests_[[method]]_counter_requests_completed_total{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "completed", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "exemplar": true, + "expr": "sum(tfs_loadgen_requests_[[method]]_counter_requests_failed_total{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "failed", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(tfs_loadgen_requests_[[method]]_counter_requests_blocked_total{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "blocked", + "range": true, + "refId": "D" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:935", + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:936", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "linear", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 2, + "interval": "60s", + "legend": { + "show": true + }, + "pluginVersion": "7.5.4", + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(\r\n max_over_time(tfs_loadgen_requests_[[method]]_histogram_duration_bucket{pod=~\"[[pod]]\"}[1m]) -\r\n min_over_time(tfs_loadgen_requests_[[method]]_histogram_duration_bucket{pod=~\"[[pod]]\"}[1m])\r\n) by (le)", + "format": "heatmap", + "instant": false, + "interval": "1m", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "title": "Histogram", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.5.22", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tfs_loadgen_requests_[[method]]_histogram_duration_sum{pod=~\"[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "total time", + "refId": "B" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Total Exec Time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:407", + "format": "s", + "logBase": 1, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:408", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": "5s", + "schemaVersion": 36, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": { + "selected": false, + "text": "setup", + "value": "setup" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "metrics(tfs_loadgen_requests_)", + "hide": 0, + "includeAll": false, + "label": "Method", + "multi": false, + "name": "method", + "options": [], + "query": { + "query": "metrics(tfs_loadgen_requests_)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "/tfs_loadgen_requests_(.+)_histogram_duration_bucket/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(tfs_loadgen_requests_[[method]]_histogram_duration_bucket, pod)", + "hide": 0, + "includeAll": true, + "label": "Pod", + "multi": true, + "name": "pod", + "options": [], + "query": { + "query": "label_values(tfs_loadgen_requests_[[method]]_histogram_duration_bucket, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "TFS / Load Generator Status", + "uid": "tfs-loadgen-stats", + "version": 3, + "weekStart": "" + } +} diff --git a/src/webui/grafana_prom_service_handler.json b/src/webui/grafana_prom_service_handler.json new file mode 100644 index 0000000000000000000000000000000000000000..86f4b13d6f654af7d1f32f865ae2f6f508b7296c --- /dev/null +++ b/src/webui/grafana_prom_service_handler.json @@ -0,0 +1,432 @@ +{"overwrite": true, "folderId": 0, "dashboard": + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1671319012315, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tfs_service_handler_[[method]]_counter_requests_started_total{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "started", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum(tfs_service_handler_[[method]]_counter_requests_completed_total{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "completed", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(tfs_service_handler_[[method]]_counter_requests_failed_total{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "failed", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:935", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:936", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "linear", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "max": null, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 6 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 2, + "interval": "60s", + "legend": { + "show": true + }, + "pluginVersion": "7.5.4", + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(\r\n max_over_time(tfs_service_handler_[[method]]_histogram_duration_bucket{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"}[1m]) -\r\n min_over_time(tfs_service_handler_[[method]]_histogram_duration_bucket{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"}[1m])\r\n) by (le)", + "format": "heatmap", + "instant": false, + "interval": "1m", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "timeFrom": null, + "title": "Histogram", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "s", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tfs_service_handler_[[method]]_histogram_duration_sum{handler=~\"[[handler]]\", pod=~\"serviceservice-[[pod]]\"})", + "hide": false, + "interval": "", + "legendFormat": "total time", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total Exec Time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transformations": [], + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:407", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:408", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": "", + "current": { + "selected": false, + "text": "setendpoint", + "value": "setendpoint" + }, + "datasource": "prometheus", + "definition": "metrics(tfs_service_handler_.+)", + "description": null, + "error": null, + "hide": 0, + "includeAll": false, + "label": "Method", + "multi": false, + "name": "method", + "options": [], + "query": { + "query": "metrics(tfs_service_handler_.+)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "/tfs_service_handler_(.+)_histogram_duration_bucket/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "prometheus", + "definition": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, handler)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Handler", + "multi": true, + "name": "handler", + "options": [], + "query": { + "query": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, handler)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "prometheus", + "definition": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, pod)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Pod", + "multi": true, + "name": "pod", + "options": [], + "query": { + "query": "label_values(tfs_service_handler_[[method]]_histogram_duration_bucket, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "/serviceservice-(.*)/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "TFS / Service / Handler", + "uid": "tfs-svc-hdlr", + "version": 16 + } +} diff --git a/src/webui/old/grafana_prom_device_config_exec_details.json b/src/webui/old/grafana_prom_device_config_exec_details.json new file mode 100644 index 0000000000000000000000000000000000000000..4b29a8dcaa99d0527e188790c2a1ff9ca000738a --- /dev/null +++ b/src/webui/old/grafana_prom_device_config_exec_details.json @@ -0,0 +1,184 @@ +{"overwrite": true, "folderId": 0, "dashboard": + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1682003744753, + "links": [], + "panels": [ + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "linear", + "colorScheme": "interpolateRdYlGn", + "exponent": 0.5, + "max": null, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": "prometheus", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 22, + "w": 24, + "x": 0, + "y": 0 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 2, + "interval": "60s", + "legend": { + "show": true + }, + "pluginVersion": "7.5.4", + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(\r\n max_over_time(tfs_device_exec_details_configuredevice_histogram_duration_bucket{pod=~\"[[pod]]\", step_name=~\"[[step_name]]\"}[1m]) -\r\n min_over_time(tfs_device_exec_details_configuredevice_histogram_duration_bucket{pod=~\"[[pod]]\", step_name=~\"[[step_name]]\"}[1m])\r\n) by (le)", + "format": "heatmap", + "instant": false, + "interval": "1m", + "intervalFactor": 1, + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "title": "Histogram", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "s", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "prometheus", + "definition": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, pod)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Pod", + "multi": true, + "name": "pod", + "options": [], + "query": { + "query": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "prometheus", + "definition": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, step_name)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Step Name", + "multi": true, + "name": "step_name", + "options": [], + "query": { + "query": "label_values(tfs_device_exec_details_configuredevice_histogram_duration_bucket, step_name)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "TFS / ConfigureDevice Details", + "uid": "tfs-dev-confdev", + "version": 4 + } +} diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index fca1071419b3b2b61739c2a0d1d8bfa45aba5119..e7f50ed42d19921b3423617f6860b5630e93adba 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -95,6 +95,9 @@ def create_app(use_config=None, web_app_root=None): from webui.service.link.routes import link # pylint: disable=import-outside-toplevel app.register_blueprint(link) + from webui.service.policy.routes import policy # pylint: disable=import-outside-toplevel + app.register_blueprint(policy) + app.jinja_env.globals.update({ # pylint: disable=no-member 'enumerate' : enumerate, 'json_to_list' : json_to_list, diff --git a/src/webui/service/link/routes.py b/src/webui/service/link/routes.py index 0fda8958e2ab2609969d2c1f68aaae61b7360b68..3ab320d8b13d037e195f00ced9eb63bd14ecc0dd 100644 --- a/src/webui/service/link/routes.py +++ b/src/webui/service/link/routes.py @@ -13,8 +13,8 @@ # limitations under the License. -from flask import render_template, Blueprint, flash, session, redirect, url_for -from common.proto.context_pb2 import Empty, Link, LinkList +from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for +from common.proto.context_pb2 import Empty, Link, LinkId, LinkList from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.Link import get_link from common.tools.context_queries.Topology import get_topology @@ -65,3 +65,25 @@ def detail(link_uuid: str): device_names, endpoints_data = get_endpoint_names(context_client, link_obj.link_endpoint_ids) context_client.close() return render_template('link/detail.html',link=link_obj, device_names=device_names, endpoints_data=endpoints_data) + +@link.get('<path:link_uuid>/delete') +def delete(link_uuid): + try: + + # first, check if link exists! + # request: LinkId = LinkId() + # request.link_uuid.uuid = link_uuid + # response: Link = client.GetLink(request) + # TODO: finalize implementation + + request = LinkId() + request.link_uuid.uuid = link_uuid # pylint: disable=no-member + context_client.connect() + context_client.RemoveLink(request) + context_client.close() + + flash(f'Link "{link_uuid}" deleted successfully!', 'success') + except Exception as e: # pylint: disable=broad-except + flash(f'Problem deleting link "{link_uuid}": {e.details()}', 'danger') + current_app.logger.exception(e) + return redirect(url_for('link.home')) diff --git a/src/webui/service/load_gen/forms.py b/src/webui/service/load_gen/forms.py index 4e0020b04f33152de382f5b93af9735f8d737f92..4c5c095cd0ca7b0549a14be394e517694d9b3268 100644 --- a/src/webui/service/load_gen/forms.py +++ b/src/webui/service/load_gen/forms.py @@ -14,11 +14,19 @@ from flask_wtf import FlaskForm from wtforms import BooleanField, FloatField, IntegerField, StringField, SubmitField -from wtforms.validators import DataRequired, NumberRange +from wtforms.validators import DataRequired, NumberRange, Regexp +from load_generator.tools.ListScalarRange import RE_SCALAR_RANGE_LIST + +DEFAULT_AVAILABILITY = '0.0..99.9999' +DEFAULT_CAPACITY_GBPS = '0.1..100.00' #'10, 40, 50, 100, 400' +DEFAULT_E2E_LATENCY_MS = '5.0..100.00' + +DEFAULT_REGEX = r'.+' class LoadGenForm(FlaskForm): num_requests = IntegerField('Num Requests', default=100, validators=[DataRequired(), NumberRange(min=0)]) num_generated = IntegerField('Num Generated', default=0, render_kw={'readonly': True}) + num_released = IntegerField('Num Released', default=0, render_kw={'readonly': True}) request_type_service_l2nm = BooleanField('Service L2NM', default=False) request_type_service_l3nm = BooleanField('Service L3NM', default=False) @@ -27,10 +35,19 @@ class LoadGenForm(FlaskForm): request_type_slice_l2nm = BooleanField('Slice L2NM', default=True) request_type_slice_l3nm = BooleanField('Slice L3NM', default=False) + device_regex = StringField('Device selector [regex]', default=DEFAULT_REGEX) + endpoint_regex = StringField('Endpoint selector [regex]', default=DEFAULT_REGEX) + offered_load = FloatField('Offered Load [Erlang]', default=50, validators=[NumberRange(min=0.0)]) holding_time = FloatField('Holding Time [seconds]', default=10, validators=[NumberRange(min=0.0)]) inter_arrival_time = FloatField('Inter Arrival Time [seconds]', default=0, validators=[NumberRange(min=0.0)]) + availability = StringField('Availability [%]', default=DEFAULT_AVAILABILITY, validators=[Regexp(RE_SCALAR_RANGE_LIST)]) + capacity_gbps = StringField('Capacity [Gbps]', default=DEFAULT_CAPACITY_GBPS, validators=[Regexp(RE_SCALAR_RANGE_LIST)]) + e2e_latency_ms = StringField('E2E Latency [ms]', default=DEFAULT_E2E_LATENCY_MS, validators=[Regexp(RE_SCALAR_RANGE_LIST)]) + + max_workers = IntegerField('Max Workers', default=10, validators=[DataRequired(), NumberRange(min=1)]) + do_teardown = BooleanField('Do Teardown', default=True) record_to_dlt = BooleanField('Record to DLT', default=False) diff --git a/src/webui/service/load_gen/routes.py b/src/webui/service/load_gen/routes.py index 5f47f06b0ff59ad1383aab94caa41adc08440c87..3483c2a65d08f3da18b2f630dbf7a59ac0f22ecb 100644 --- a/src/webui/service/load_gen/routes.py +++ b/src/webui/service/load_gen/routes.py @@ -17,6 +17,8 @@ from flask import redirect, render_template, Blueprint, flash, url_for from common.proto.context_pb2 import Empty from common.proto.load_generator_pb2 import Parameters, RequestTypeEnum from load_generator.client.LoadGeneratorClient import LoadGeneratorClient +from load_generator.tools.ListScalarRange import ( + list_scalar_range__grpc_to_str, list_scalar_range__list_to_grpc, parse_list_scalar_range) from .forms import LoadGenForm load_gen = Blueprint('load_gen', __name__, url_prefix='/load_gen') @@ -55,23 +57,34 @@ def home(): _holding_time = round(status.parameters.holding_time , ndigits=4) _inter_arrival_time = round(status.parameters.inter_arrival_time , ndigits=4) + _availability = list_scalar_range__grpc_to_str(status.parameters.availability ) + _capacity_gbps = list_scalar_range__grpc_to_str(status.parameters.capacity_gbps ) + _e2e_latency_ms = list_scalar_range__grpc_to_str(status.parameters.e2e_latency_ms) + form = LoadGenForm() - set_properties(form.num_requests , status.parameters.num_requests , readonly=status.running) - set_properties(form.offered_load , _offered_load , readonly=status.running) - set_properties(form.holding_time , _holding_time , readonly=status.running) - set_properties(form.inter_arrival_time , _inter_arrival_time , readonly=status.running) - set_properties(form.do_teardown , status.parameters.do_teardown , disabled=status.running) - set_properties(form.record_to_dlt , status.parameters.record_to_dlt, disabled=status.running) - set_properties(form.dlt_domain_id , status.parameters.dlt_domain_id, readonly=status.running) - set_properties(form.request_type_service_l2nm, _request_type_service_l2nm , disabled=status.running) - set_properties(form.request_type_service_l3nm, _request_type_service_l3nm , disabled=status.running) - set_properties(form.request_type_service_mw , _request_type_service_mw , disabled=status.running) - set_properties(form.request_type_service_tapi, _request_type_service_tapi , disabled=status.running) - set_properties(form.request_type_slice_l2nm , _request_type_slice_l2nm , disabled=status.running) - set_properties(form.request_type_slice_l3nm , _request_type_slice_l3nm , disabled=status.running) - set_properties(form.num_generated , status.num_generated , disabled=True) - set_properties(form.infinite_loop , status.infinite_loop , disabled=True) - set_properties(form.running , status.running , disabled=True) + set_properties(form.num_requests , status.parameters.num_requests , readonly=status.running) + set_properties(form.device_regex , status.parameters.device_regex , readonly=status.running) + set_properties(form.endpoint_regex , status.parameters.endpoint_regex, readonly=status.running) + set_properties(form.offered_load , _offered_load , readonly=status.running) + set_properties(form.holding_time , _holding_time , readonly=status.running) + set_properties(form.inter_arrival_time , _inter_arrival_time , readonly=status.running) + set_properties(form.availability , _availability , readonly=status.running) + set_properties(form.capacity_gbps , _capacity_gbps , readonly=status.running) + set_properties(form.e2e_latency_ms , _e2e_latency_ms , readonly=status.running) + set_properties(form.max_workers , status.parameters.max_workers , readonly=status.running) + set_properties(form.do_teardown , status.parameters.do_teardown , disabled=status.running) + set_properties(form.record_to_dlt , status.parameters.record_to_dlt , disabled=status.running) + set_properties(form.dlt_domain_id , status.parameters.dlt_domain_id , readonly=status.running) + set_properties(form.request_type_service_l2nm, _request_type_service_l2nm , disabled=status.running) + set_properties(form.request_type_service_l3nm, _request_type_service_l3nm , disabled=status.running) + set_properties(form.request_type_service_mw , _request_type_service_mw , disabled=status.running) + set_properties(form.request_type_service_tapi, _request_type_service_tapi , disabled=status.running) + set_properties(form.request_type_slice_l2nm , _request_type_slice_l2nm , disabled=status.running) + set_properties(form.request_type_slice_l3nm , _request_type_slice_l3nm , disabled=status.running) + set_properties(form.num_generated , status.num_generated , disabled=True) + set_properties(form.num_released , status.num_released , disabled=True) + set_properties(form.infinite_loop , status.infinite_loop , disabled=True) + set_properties(form.running , status.running , disabled=True) form.submit.label.text = 'Stop' if status.running else 'Start' form_action = url_for('load_gen.stop') if status.running else url_for('load_gen.start') @@ -82,16 +95,27 @@ def start(): form = LoadGenForm() if form.validate_on_submit(): try: + _availability = parse_list_scalar_range(form.availability.data ) + _capacity_gbps = parse_list_scalar_range(form.capacity_gbps.data ) + _e2e_latency_ms = parse_list_scalar_range(form.e2e_latency_ms.data) + load_gen_params = Parameters() load_gen_params.num_requests = form.num_requests.data + load_gen_params.device_regex = form.device_regex.data + load_gen_params.endpoint_regex = form.endpoint_regex.data load_gen_params.offered_load = form.offered_load.data load_gen_params.holding_time = form.holding_time.data load_gen_params.inter_arrival_time = form.inter_arrival_time.data + load_gen_params.max_workers = form.max_workers.data load_gen_params.do_teardown = form.do_teardown.data load_gen_params.dry_mode = False load_gen_params.record_to_dlt = form.record_to_dlt.data load_gen_params.dlt_domain_id = form.dlt_domain_id.data + list_scalar_range__list_to_grpc(_availability, load_gen_params.availability ) # pylint: disable=no-member + list_scalar_range__list_to_grpc(_capacity_gbps, load_gen_params.capacity_gbps ) # pylint: disable=no-member + list_scalar_range__list_to_grpc(_e2e_latency_ms, load_gen_params.e2e_latency_ms) # pylint: disable=no-member + del load_gen_params.request_types[:] # pylint: disable=no-member request_types = list() if form.request_type_service_l2nm.data: request_types.append(RequestTypeEnum.REQUESTTYPE_SERVICE_L2NM) diff --git a/src/webui/service/policy/__init__.py b/src/webui/service/policy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/webui/service/policy/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# 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. + diff --git a/src/webui/service/policy/routes.py b/src/webui/service/policy/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..6d14f86b4f1428695b474b3f2e2dd4dc72657452 --- /dev/null +++ b/src/webui/service/policy/routes.py @@ -0,0 +1,50 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# 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. + +import grpc +from flask import render_template, Blueprint +from common.proto.context_pb2 import Empty +from common.proto.policy_pb2 import PolicyRuleStateEnum +from context.client.ContextClient import ContextClient + +policy = Blueprint('policy', __name__, url_prefix='/policy') + +context_client = ContextClient() + +@policy.get('/') +def home(): + context_client.connect() + policy_rules = context_client.ListPolicyRules(Empty()) + policy_rules = policy_rules.policyRules + context_client.close() + return render_template('policy/home.html', policy_rules=policy_rules, prse=PolicyRuleStateEnum) + +#@policy.get('<path:policy_uuid>/detail') +#def detail(policy_uuid: str): +# try: +# context_client.connect() +# +# slice_obj = get_slice_by_uuid(context_client, slice_uuid, rw_copy=False) +# if slice_obj is None: +# flash('Context({:s})/Slice({:s}) not found'.format(str(context_uuid), str(slice_uuid)), 'danger') +# slice_obj = Slice() +# +# context_client.close() +# +# return render_template( +# 'slice/detail.html', slice=slice_obj, prse=PolicyRuleStateEnum) +# except Exception as e: +# flash('The system encountered an error and cannot show the details of this slice.', 'warning') +# current_app.logger.exception(e) +# return redirect(url_for('slice.home')) diff --git a/src/webui/service/templates/base.html b/src/webui/service/templates/base.html index 1dfa3687198d8a33db346ba2bbcd2989f6f109bb..61c283b0d957b4d13b7cc57e47d3ea2675ab76f0 100644 --- a/src/webui/service/templates/base.html +++ b/src/webui/service/templates/base.html @@ -83,6 +83,13 @@ <a class="nav-link" href="{{ url_for('slice.home') }}">Slice</a> {% endif %} </li> + <li class="nav-item"> + {% if '/policy/' in request.path %} + <a class="nav-link active" aria-current="page" href="{{ url_for('policy.home') }}">Policy</a> + {% else %} + <a class="nav-link" href="{{ url_for('policy.home') }}">Policy</a> + {% endif %} + </li> <li class="nav-item"> <a class="nav-link" href="/grafana" id="grafana_link" target="grafana">Grafana</a> </li> diff --git a/src/webui/service/templates/link/detail.html b/src/webui/service/templates/link/detail.html index 916abafde05b3ec990346ff7966f207b1dafc10a..8ca7faee3e1871d11b819c6ca95668e654041f8c 100644 --- a/src/webui/service/templates/link/detail.html +++ b/src/webui/service/templates/link/detail.html @@ -13,62 +13,92 @@ See the License for the specific language governing permissions and limitations under the License. --> - {% extends 'base.html' %} - - {% block content %} - <h1>Link {{ link.name }} ({{ link.link_id.link_uuid.uuid }})</h1> - <div class="row mb-3"> - <div class="col-sm-3"> - <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('link.home') }}'"> - <i class="bi bi-box-arrow-in-left"></i> - Back to link list - </button> - </div> - </div> - <br> - <div class="row mb-3"> - <div class="col-sm-4"> - <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br> - <b>Name: </b>{{ link.name }}<br> - </div> - <div class="col-sm-8"> - <table class="table table-striped table-hover"> - <thead> - <tr> - <th scope="col">Endpoint UUID</th> - <th scope="col">Name</th> - <th scope="col">Device</th> - <th scope="col">Endpoint Type</th> - </tr> - </thead> - <tbody> - {% for endpoint in link.link_endpoint_ids %} - <tr> - <td> - {{ endpoint.endpoint_uuid.uuid }} - </td> - <td> - {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} - </td> - <td> - <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> - {{ device_names.get(endpoint.device_id.device_uuid.uuid, endpoint.device_id.device_uuid.uuid) }} - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> - <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> - <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> - </svg> - </a> - </td> - <td> - {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, ('', '-'))[1] }} - </td> - </tr> - {% endfor %} - </tbody> - </table> +{% extends 'base.html' %} + +{% block content %} +<h1>Link {{ link.name }} ({{ link.link_id.link_uuid.uuid }})</h1> +<div class="row mb-3"> + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('link.home') }}'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to link list + </button> + </div> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete link</button> --> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i> + Delete link + </button> + </div> +</div> + +<br> +<div class="row mb-3"> + <div class="col-sm-4"> + <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br> + <b>Name: </b>{{ link.name }}<br> + </div> + <div class="col-sm-8"> + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Endpoint UUID</th> + <th scope="col">Name</th> + <th scope="col">Device</th> + <th scope="col">Endpoint Type</th> + </tr> + </thead> + <tbody> + {% for endpoint in link.link_endpoint_ids %} + <tr> + <td> + {{ endpoint.endpoint_uuid.uuid }} + </td> + <td> + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} + </td> + <td> + <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> + {{ device_names.get(endpoint.device_id.device_uuid.uuid, endpoint.device_id.device_uuid.uuid) }} + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> + <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> + <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> + </svg> + </a> + </td> + <td> + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, ('', '-'))[1] }} + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + + +<!-- Modal --> +<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" + aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="staticBackdropLabel">Delete link?</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + Are you sure you want to delete the link "{{ link.link_id.link_uuid.uuid }}"? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> + <a type="button" class="btn btn-danger" + href="{{ url_for('link.delete', link_uuid=link.link_id.link_uuid.uuid) }}"><i + class="bi bi-exclamation-diamond"></i>Yes</a> </div> </div> + </div> +</div> - {% endblock %} - \ No newline at end of file +{% endblock %} diff --git a/src/webui/service/templates/load_gen/home.html b/src/webui/service/templates/load_gen/home.html index d58f42601925ca438ab9d9f20b32f94960b5cada..cec0a38dba2b4b31d4d07d391e4ce211f0c7ac76 100644 --- a/src/webui/service/templates/load_gen/home.html +++ b/src/webui/service/templates/load_gen/home.html @@ -53,6 +53,21 @@ </div> <br /> + <div class="row mb-3"> + {{ form.num_released.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.num_released.errors %} + {{ form.num_released(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.num_released.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.num_released(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="row mb-3"> <div class="col-sm-2 col-form-label">Service Types:</div> <div class="col-sm-10"> @@ -68,6 +83,36 @@ </div> <br /> + <div class="row mb-3"> + {{ form.device_regex.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_regex.errors %} + {{ form.device_regex(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.device_regex.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.device_regex(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + + <div class="row mb-3"> + {{ form.endpoint_regex.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.endpoint_regex.errors %} + {{ form.endpoint_regex(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.endpoint_regex.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.endpoint_regex(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="row mb-3"> {{ form.offered_load.label(class="col-sm-2 col-form-label") }} <div class="col-sm-10"> @@ -113,6 +158,66 @@ </div> <br /> + <div class="row mb-3"> + {{ form.availability.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.availability.errors %} + {{ form.availability(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.availability.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.availability(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + + <div class="row mb-3"> + {{ form.capacity_gbps.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.capacity_gbps.errors %} + {{ form.capacity_gbps(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.capacity_gbps.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.capacity_gbps(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + + <div class="row mb-3"> + {{ form.e2e_latency_ms.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.e2e_latency_ms.errors %} + {{ form.e2e_latency_ms(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.e2e_latency_ms.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.e2e_latency_ms(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + + <div class="row mb-3"> + {{ form.max_workers.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.max_workers.errors %} + {{ form.max_workers(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.max_workers.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.max_workers(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="row mb-3"> <div class="col-sm-10"> {{ form.do_teardown }} {{ form.do_teardown.label(class="col-sm-3 col-form-label") }}<br/> diff --git a/src/webui/service/templates/policy/home.html b/src/webui/service/templates/policy/home.html new file mode 100644 index 0000000000000000000000000000000000000000..081a7f0b5291346633a2f682ba4552b5c1e362fb --- /dev/null +++ b/src/webui/service/templates/policy/home.html @@ -0,0 +1,84 @@ +<!-- + Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) + + 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. +--> + +{% extends 'base.html' %} + +{% block content %} + <h1>Policy</h1> + + <div class="row"> + <div class="col"> + {{ policies | length }} policies found in context <i>{{ session['context_uuid'] }}</i> + </div> + </div> + + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">UUID</th> + <th scope="col">Kind</th> + <th scope="col">Priority</th> + <th scope="col">Condition</th> + <th scope="col">Operator</th> + <th scope="col">Action</th> + <th scope="col">Service</th> + <th scope="col">Devices</th> + <th scope="col">State</th> + <th scope="col">Message</th> + <th scope="col">Extra</th> + <th scope="col"></th> + </tr> + </thead> + <tbody> + {% if policies %} + {% for policy in policies %} + {% if policy.WhichOneof('policy_rule') == 'device' %} + <tr> + <td>{{ policy.device.policyRuleBasic.policyRuleId.uuid }}</td> + <td>{{ policy.WhichOneof('policy_rule') }}</td> + <td>{{ policy.device.policyRuleBasic.priority }}</td> + <td>{{ policy.device.policyRuleBasic.conditionList }}</td> + <td>{{ policy.device.policyRuleBasic.booleanOperator }}</td> + <td>{{ policy.device.policyRuleBasic.actionList }}</td> + <td>-</td> + <td>{{ policy.device.deviceList }}</td> + <td>{{ prse.Name(policy.device.policyRuleBasic.policyRuleState.policyRuleState).replace('POLICY_', '') }}</td> + <td>{{ policy.device.policyRuleBasic.policyRuleState.policyRuleStateMessage }}</td> + </tr> + {% elif policy.WhichOneof('policy_rule') == 'service' %} + <tr> + <td>{{ policy.service.policyRuleBasic.policyRuleId.uuid }}</td> + <td>{{ policy.WhichOneof('policy_rule') }}</td> + <td>{{ policy.service.policyRuleBasic.priority }}</td> + <td>{{ policy.service.policyRuleBasic.conditionList }}</td> + <td>{{ policy.service.policyRuleBasic.booleanOperator }}</td> + <td>{{ policy.service.policyRuleBasic.actionList }}</td> + <td>{{ policy.service.serviceId }}</td> + <td>{{ policy.service.deviceList }}</td> + <td>{{ prse.Name(policy.service.policyRuleBasic.policyRuleState.policyRuleState).replace('POLICY_', '') }}</td> + <td>{{ policy.service.policyRuleBasic.policyRuleState.policyRuleStateMessage }}</td> + </tr> + {% else %} + <tr><td colspan="11">Unsupported policy type {{ policy.WhichOneof('policy_rule') }}</td></tr> + {% endif %} + {% endfor %} + {% else %} + <tr><td colspan="11">No policies found</td></tr> + {% endif %} + </tbody> + </table> + +{% endblock %}