On 17 Jan 2025, at 15:25, Adrian Moreno wrote:

> The upcall_cost.py script has a useful port mapping cache that can be
> very useful for other scripts.
>
> Move it into a library file (usdt_lib.py). While we're at it, refactor
> it into a class since setting attributes to functions and having it
> return different types depending on input is not very clean.
>
> This patch should not introduce any functional change.
>
> Signed-off-by: Adrian Moreno <[email protected]>

In general, this looks good to me. Some small comments below.

//Eelco

> ---
>  utilities/automake.mk                 |  7 ++-
>  utilities/usdt-scripts/upcall_cost.py | 72 ++++-----------------------
>  utilities/usdt-scripts/usdt_lib.py    | 70 ++++++++++++++++++++++++++
>  3 files changed, 86 insertions(+), 63 deletions(-)
>  create mode 100644 utilities/usdt-scripts/usdt_lib.py
>
> diff --git a/utilities/automake.mk b/utilities/automake.mk
> index 22260b254..fcd353109 100644
> --- a/utilities/automake.mk
> +++ b/utilities/automake.mk
> @@ -28,7 +28,8 @@ usdt_SCRIPTS += \
>       utilities/usdt-scripts/kernel_delay.rst \
>       utilities/usdt-scripts/reval_monitor.py \
>       utilities/usdt-scripts/upcall_cost.py \
> -     utilities/usdt-scripts/upcall_monitor.py
> +     utilities/usdt-scripts/upcall_monitor.py \
> +     utilities/usdt-scripts/usdt_lib.py
>
>  completion_SCRIPTS += \
>       utilities/ovs-appctl-bashcomp.bash \
> @@ -78,7 +79,9 @@ EXTRA_DIST += \
>       utilities/usdt-scripts/kernel_delay.rst \
>       utilities/usdt-scripts/reval_monitor.py \
>       utilities/usdt-scripts/upcall_cost.py \
> -     utilities/usdt-scripts/upcall_monitor.py
> +     utilities/usdt-scripts/upcall_monitor.py \
> +     utilities/usdt-scripts/usdt_lib.py
> +
>  MAN_ROOTS += \
>       utilities/ovs-testcontroller.8.in \
>       utilities/ovs-dpctl.8.in \
> diff --git a/utilities/usdt-scripts/upcall_cost.py 
> b/utilities/usdt-scripts/upcall_cost.py
> index 765669585..47a1e30a6 100755
> --- a/utilities/usdt-scripts/upcall_cost.py
> +++ b/utilities/usdt-scripts/upcall_cost.py
> @@ -58,12 +58,13 @@ from strenum import StrEnum
>  from text_histogram3 import histogram
>  from time import process_time
>
> +from usdt_lib import DpPortMapping
> +
>  import argparse
>  import ast
>  import psutil
>  import re
>  import struct
> -import subprocess
>  import sys
>  import time
>
> @@ -353,7 +354,7 @@ class DpUpcall(Event):
>                   pkt_frag_len):
>          super(DpUpcall, self).__init__(ts, pid, comm, cpu, 
> EventType.DP_UPCALL)
>          self.dpif_name = dpif_name
> -        self.dp_port = get_dp_mapping(dpif_name, port)
> +        self.dp_port = dp_map.get(dpif_name, port)
>          if self.dp_port is None:
>              #
>              # As we only identify interfaces at startup, new interfaces could
> @@ -446,13 +447,8 @@ class RecvUpcall(Event):
>                  self.pkt_len)
>
>      def get_system_dp_port(dpif_name):
> -        dp_map = get_dp_mapping(dpif_name, "ovs-system", return_map=True)
> -        if dpif_name not in dp_map:
> -            return None
> -        try:
> -            return dp_map[dpif_name]["ovs-system"]
> -        except KeyError:
> -            return None
> +        dp_map = dp_map.get_map()

This looks confusing, as dp_map local is the same as the global variable. Maybe 
rename it.

> +        return dp_map.get(dpif_name, {}).get("ovs-system", None)
>
>      def decode_nlm(msg, indent=4, dump=True):
>          bytes_left = len(msg)
> @@ -621,54 +617,6 @@ class OpFlowExecute(Event):
>          return event
>
>
> -#
> -# get_dp_mapping()
> -#
> -def get_dp_mapping(dp, port, return_map=False, dp_map=None):
> -    if options.unit_test:
> -        return port
> -
> -    if dp_map is not None:
> -        get_dp_mapping.dp_port_map_cache = dp_map
> -
> -    #
> -    # Build a cache, so we do not have to execue the ovs command each time.
> -    #
> -    if not hasattr(get_dp_mapping, "dp_port_map_cache"):
> -        try:
> -            output = subprocess.check_output(['ovs-appctl', 'dpctl/show'],
> -                                             encoding='utf8').split("\n")
> -        except subprocess.CalledProcessError:
> -            output = ""
> -            pass
> -
> -        current_dp = None
> -        get_dp_mapping.dp_port_map_cache = {}
> -
> -        for line in output:
> -            match = re.match("^system@(.*):$", line)
> -            if match is not None:
> -                current_dp = match.group(1)
> -
> -            match = re.match("^  port ([0-9]+): ([^ /]*)", line)
> -            if match is not None and current_dp is not None:
> -                try:
> -                    get_dp_mapping.dp_port_map_cache[
> -                        current_dp][match.group(2)] = int(match.group(1))
> -                except KeyError:
> -                    get_dp_mapping.dp_port_map_cache[current_dp] = \
> -                        {match.group(2): int(match.group(1))}
> -
> -    if return_map:
> -        return get_dp_mapping.dp_port_map_cache
> -
> -    if dp not in get_dp_mapping.dp_port_map_cache or \
> -       port not in get_dp_mapping.dp_port_map_cache[dp]:
> -        return None
> -
> -    return get_dp_mapping.dp_port_map_cache[dp][port]
> -
> -
>  #
>  # event_to_dict()
>  #
> @@ -1429,6 +1377,9 @@ def main():
>      global events_received
>      global event_count
>      global export_file
> +    global dp_map
> +
> +    dp_map = DpPortMapping()
>
>      #
>      # Argument parsing
> @@ -1531,9 +1482,9 @@ def main():
>      event_count = {'total': {}, 'valid': {}, 'miss': {}}
>      if options.read_events is None:
>          #
> -        # Call get_dp_mapping() to prepare the cache
> +        # Prepare the datapath port mapping cache
>          #
> -        dp_port_map = get_dp_mapping("ovs-system", "eth0", return_map=True)
> +        dp_port_map = dp_map.get_map()
>          if export_file is not None:
>              export_file.write("dp_port_map = {}\n".format(dp_port_map))
>
> @@ -1643,8 +1594,7 @@ def main():
>                      if entry.startswith('dp_port_map = {'):
>                          if not dp_port_mapping_valid:
>                              dp_port_mapping_valid = True
> -                            get_dp_mapping("", "",
> -                                           
> dp_map=ast.literal_eval(entry[14:]))
> +                            dp_map.set_map(ast.literal_eval(entry[14:]))
>                      elif (entry.startswith('event = {') and
>                            dp_port_mapping_valid):
>                          event = ast.literal_eval(entry[8:])
> diff --git a/utilities/usdt-scripts/usdt_lib.py 
> b/utilities/usdt-scripts/usdt_lib.py
> new file mode 100644
> index 000000000..da3fab2bf
> --- /dev/null
> +++ b/utilities/usdt-scripts/usdt_lib.py
> @@ -0,0 +1,70 @@
> +# Copyright (c) 2025 Red Hat, Inc.

As some of this code is copied from the 2021 licensed file, I think, this 
should also be 2021.

> +#
> +# 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 re
> +import subprocess
> +
> +
> +class DpPortMapping:
> +    """Class used to retrieve and cache datapath port numbers to port 
> names."""
> +
> +    def __init__(self):
> +        self.cache_map = None
> +
> +    def get_map(self):
> +        """Get the cache map."""
> +        if not self.cache_map:
> +            self._get_mapping()
> +
> +        return self.cache_map
> +
> +    def set_map(self, cache_map):
> +        """Override the internal cache map."""
> +        self.cache_map = cache_map
> +
> +    def get(self, dp, port_no):
> +        """Get the portname from a port number."""
> +        if self.cache_map is None:
> +            self._get_mapping()
> +
> +        return self.cache_map.get(dp, {}).get(port_no, None)
> +
> +    def _get_mapping(self):
> +        """Get the datapath port mapping from the running OVS."""
> +        try:
> +            output = subprocess.check_output(
> +                ["ovs-appctl", "dpctl/show"], encoding="utf8"
> +            ).split("\n")
> +        except subprocess.CalledProcessError:
> +            output = ""
> +            return
> +
> +        current_dp = None
> +        self.cache_map = {}
> +
> +        for line in output:
> +            match = re.match("^system@(.*):$", line)
> +            if match:
> +                current_dp = match.group(1)
> +
> +            match = re.match("^  port ([0-9]+): ([^ /]*)", line)
> +            if match and current_dp:
> +                try:
> +                    self.cache_map[current_dp][match.group(2)] = int(
> +                        match.group(1)
> +                    )
> +                except KeyError:
> +                    self.cache_map[current_dp] = {
> +                        match.group(2): int(match.group(1))
> +                    }
> -- 
> 2.48.1
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to