refactor(trex): Startup configuration 42/38842/12
authorpmikus <peter.mikus@protonmail.ch>
Thu, 18 May 2023 05:59:39 +0000 (05:59 +0000)
committerPeter Mikus <peter.mikus@protonmail.ch>
Wed, 24 May 2023 05:08:08 +0000 (05:08 +0000)
Signed-off-by: pmikus <peter.mikus@protonmail.ch>
Change-Id: I16defefa5edd01638bc382be4f5e8cbca4fe9453

resources/libraries/python/CpuUtils.py
resources/libraries/python/TRexConfigGenerator.py [new file with mode: 0644]
resources/libraries/python/TrafficGenerator.py

index 5805ba7..1e306f0 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2021 Cisco and/or its affiliates.
+# Copyright (c) 2023 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -388,25 +388,23 @@ class CpuUtils:
 
     @staticmethod
     def get_affinity_trex(
 
     @staticmethod
     def get_affinity_trex(
-            node, if1_pci, if2_pci, tg_mtc=1, tg_dtc=1, tg_ltc=1):
+            node, if_key, tg_mtc=1, tg_dtc=1, tg_ltc=1):
         """Get affinity for T-Rex. Result will be used to pin T-Rex threads.
 
         :param node: TG node.
         """Get affinity for T-Rex. Result will be used to pin T-Rex threads.
 
         :param node: TG node.
-        :param if1_pci: TG first interface.
-        :param if2_pci: TG second interface.
+        :param if_key: TG first interface.
         :param tg_mtc: TG main thread count.
         :param tg_dtc: TG dataplane thread count.
         :param tg_ltc: TG latency thread count.
         :type node: dict
         :param tg_mtc: TG main thread count.
         :param tg_dtc: TG dataplane thread count.
         :param tg_ltc: TG latency thread count.
         :type node: dict
-        :type if1_pci: str
-        :type if2_pci: str
+        :type if_key: str
         :type tg_mtc: int
         :type tg_dtc: int
         :type tg_ltc: int
         :returns: List of CPUs allocated to T-Rex including numa node.
         :rtype: int, int, int, list
         """
         :type tg_mtc: int
         :type tg_dtc: int
         :type tg_ltc: int
         :returns: List of CPUs allocated to T-Rex including numa node.
         :rtype: int, int, int, list
         """
-        interface_list = [if1_pci, if2_pci]
+        interface_list = [if_key]
         cpu_node = Topology.get_interfaces_numa_node(node, *interface_list)
 
         master_thread_id = CpuUtils.cpu_slice_of_list_per_node(
         cpu_node = Topology.get_interfaces_numa_node(node, *interface_list)
 
         master_thread_id = CpuUtils.cpu_slice_of_list_per_node(
diff --git a/resources/libraries/python/TRexConfigGenerator.py b/resources/libraries/python/TRexConfigGenerator.py
new file mode 100644 (file)
index 0000000..2015b09
--- /dev/null
@@ -0,0 +1,287 @@
+# Copyright (c) 2023 Cisco and/or its affiliates.
+# 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.
+
+"""TRex Configuration File Generator library."""
+
+import re
+import yaml
+
+from resources.libraries.python.Constants import Constants
+from resources.libraries.python.CpuUtils import CpuUtils
+from resources.libraries.python.ssh import exec_cmd_no_error
+from resources.libraries.python.topology import NodeType, NodeSubTypeTG
+from resources.libraries.python.topology import Topology
+
+
+__all__ = ["TrexConfigGenerator", "TrexInitConfig"]
+
+def pci_dev_check(pci_dev):
+    """Check if provided PCI address is in correct format.
+
+    :param pci_dev: PCI address (expected format: xxxx:xx:xx.x).
+    :type pci_dev: str
+    :returns: True if PCI address is in correct format.
+    :rtype: bool
+    :raises ValueError: If PCI address is in incorrect format.
+    """
+    pattern = re.compile(
+        r"^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}\.[0-9A-Fa-f]$"
+    )
+    if not re.match(pattern, pci_dev):
+        raise ValueError(
+            f"PCI address {pci_dev} is not in valid format xxxx:xx:xx.x"
+        )
+    return True
+
+
+class TrexConfigGenerator:
+    """TRex Startup Configuration File Generator."""
+
+    def __init__(self):
+        """Initialize library.
+        """
+        self._node = ""
+        self._node_key = ""
+        self._node_config = dict()
+        self._node_serialized_config = ""
+        self._startup_configuration_path = "/etc/trex_cfg.yaml"
+
+    def set_node(self, node, node_key=None):
+        """Set topology node.
+
+        :param node: Node to store configuration on.
+        :param node_key: Topology node key.
+        :type node: dict
+        :type node_key: str
+        :raises RuntimeError: If Node type is not TG and subtype is not TREX.
+        """
+        if node.get("type") is None:
+            msg = "Node type is not defined!"
+        elif node["type"] != NodeType.TG:
+            msg = f"Node type is {node['type']!r}, not a TG!"
+        elif node.get("subtype") is None:
+            msg = "TG subtype is not defined"
+        elif node["subtype"] != NodeSubTypeTG.TREX:
+            msg = f"TG subtype {node['subtype']!r} is not supported"
+        else:
+            self._node = node
+            self._node_key = node_key
+            return
+        raise RuntimeError(msg)
+
+    def get_serialized_config(self):
+        """Get serialized startup configuration in YAML format.
+
+        :returns: Startup configuration in YAML format.
+        :rtype: str
+        """
+        self.serialize_config(self._node_config)
+        return self._node_serialized_config
+
+    def serialize_config(self, obj):
+        """Serialize the startup configuration in YAML format.
+
+        :param obj: Python Object to print.
+        :type obj: Obj
+        """
+        self._node_serialized_config = yaml.dump([obj], default_style=None)
+
+    def add_config_item(self, config, value, path):
+        """Add startup configuration item.
+
+        :param config: Startup configuration of node.
+        :param value: Value to insert.
+        :param path: Path where to insert item.
+        :type config: dict
+        :type value: str
+        :type path: list
+        """
+        if len(path) == 1:
+            config[path[0]] = value
+            return
+        if path[0] not in config:
+            config[path[0]] = dict()
+        elif isinstance(config[path[0]], str):
+            config[path[0]] = dict() if config[path[0]] == "" \
+                else {config[path[0]]: ""}
+        self.add_config_item(config[path[0]], value, path[1:])
+
+    def add_version(self, value=2):
+        """Add config file version.
+
+        :param value: Version of configuration file.
+        :type value: int
+        """
+        path = ["version"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_c(self, value):
+        """Add core count.
+
+        :param value: Core count.
+        :type value: int
+        """
+        path = ["c"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_limit_memory(self, value):
+        """Add memory limit.
+
+        :param value: Memory limit.
+        :type value: str
+        """
+        path = ["limit_memory"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_interfaces(self, devices):
+        """Add PCI device configuration.
+
+        :param devices: PCI device(s) (format xxxx:xx:xx.x).
+        :type devices: list(str)
+        """
+        for device in devices:
+            pci_dev_check(device)
+
+        path = ["interfaces"]
+        self.add_config_item(self._node_config, devices, path)
+
+    def add_rx_desc(self, value):
+        """Add RX descriptors.
+
+        :param value: RX descriptors count.
+        :type value: int
+        """
+        path = ["rx_desc"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_tx_desc(self, value):
+        """Add TX descriptors.
+
+        :param value: TX descriptors count.
+        :type value: int
+        """
+        path = ["tx_desc"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_port_info(self, value):
+        """Add port information configuration.
+
+        :param value: Port information configuration.
+        :type value: list(dict)
+        """
+        path = ["port_info"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_platform_master_thread_id(self, value):
+        """Add platform master thread ID.
+
+        :param value: Master thread ID.
+        :type value: int
+        """
+        path = ["platform", "master_thread_id"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_platform_latency_thread_id(self, value):
+        """Add platform latency thread ID.
+
+        :param value: Latency thread ID.
+        :type value: int
+        """
+        path = ["platform", "latency_thread_id"]
+        self.add_config_item(self._node_config, value, path)
+
+    def add_platform_dual_if(self, value):
+        """Add platform dual interface configuration.
+
+        :param value: Dual interface configuration.
+        :type value: list(dict)
+        """
+        path = ["platform", "dual_if"]
+        self.add_config_item(self._node_config, value, path)
+
+    def write_config(self, path=None):
+        """Generate and write TRex startup configuration to file.
+
+        :param path: Override startup configuration path.
+        :type path: str
+        """
+        self.serialize_config(self._node_config)
+
+        if path is None:
+            path = self._startup_configuration_path
+
+        command = f"echo \"{self._node_serialized_config}\" | sudo tee {path}"
+        message = "Writing TRex startup configuration failed!"
+        exec_cmd_no_error(self._node, command, message=message)
+
+
+class TrexInitConfig:
+    """TRex Initial Configuration.
+    """
+    @staticmethod
+    def init_trex_startup_configuration(node, tg_topology):
+        """Apply initial TRex startup configuration.
+
+        :param node: TRex node in the topology.
+        :param tg_topology: Ordered TRex links.
+        :type node: dict
+        :type tg_topology: list(dict)
+        """
+        pci_addresses = list()
+        dual_if = list()
+        port_info = list()
+        master_thread_id = None
+        latency_thread_id = None
+        cores = None
+        limit_memory = f"{Constants.TREX_LIMIT_MEMORY}"
+        sockets = 0
+
+        for link in tg_topology:
+            pci_addresses.append(
+                Topology().get_interface_pci_addr(node, link["interface"])
+            )
+            master_thread_id, latency_thread_id, socket, threads = \
+                CpuUtils.get_affinity_trex(
+                    node, link["interface"], tg_dtc=Constants.TREX_CORE_COUNT
+                )
+            dual_if.append(dict(socket=socket, threads=threads))
+            cores = len(threads)
+            port_info.append(
+                dict(
+                    src_mac=Topology().get_interface_mac(
+                        node, link["interface"]
+                    ),
+                    dst_mac=link["dst_mac"]
+                )
+            )
+            sockets = sockets | socket
+        if sockets:
+            limit_memory = (
+                f"{Constants.TREX_LIMIT_MEMORY},{Constants.TREX_LIMIT_MEMORY}"
+            )
+
+        trex_config = TrexConfigGenerator()
+        trex_config.set_node(node)
+        trex_config.add_version()
+        trex_config.add_interfaces(pci_addresses)
+        trex_config.add_c(cores)
+        trex_config.add_limit_memory(limit_memory)
+        trex_config.add_port_info(port_info)
+        if Constants.TREX_RX_DESCRIPTORS_COUNT != 0:
+            trex_config.add_rx_desc(Constants.TREX_RX_DESCRIPTORS_COUNT)
+        if Constants.TREX_TX_DESCRIPTORS_COUNT != 0:
+            trex_config.add_rx_desc(Constants.TREX_TX_DESCRIPTORS_COUNT)
+        trex_config.add_platform_master_thread_id(int(master_thread_id))
+        trex_config.add_platform_latency_thread_id(int(latency_thread_id))
+        trex_config.add_platform_dual_if(dual_if)
+        trex_config.write_config()
index 2e03b4b..46c8b01 100644 (file)
@@ -20,7 +20,6 @@ from robot.api import logger
 from robot.libraries.BuiltIn import BuiltIn
 
 from .Constants import Constants
 from robot.libraries.BuiltIn import BuiltIn
 
 from .Constants import Constants
-from .CpuUtils import CpuUtils
 from .DropRateSearch import DropRateSearch
 from .MLRsearch.AbstractMeasurer import AbstractMeasurer
 from .MLRsearch.MultipleLossRatioSearch import MultipleLossRatioSearch
 from .DropRateSearch import DropRateSearch
 from .MLRsearch.AbstractMeasurer import AbstractMeasurer
 from .MLRsearch.MultipleLossRatioSearch import MultipleLossRatioSearch
@@ -31,6 +30,7 @@ from .ssh import exec_cmd_no_error, exec_cmd
 from .topology import NodeType
 from .topology import NodeSubTypeTG
 from .topology import Topology
 from .topology import NodeType
 from .topology import NodeSubTypeTG
 from .topology import Topology
+from .TRexConfigGenerator import TrexInitConfig
 from .DUTSetup import DUTSetup as DS
 
 __all__ = [u"TGDropRateSearchImpl", u"TrafficGenerator", u"OptimizedSearch"]
 from .DUTSetup import DUTSetup as DS
 
 __all__ = [u"TGDropRateSearchImpl", u"TrafficGenerator", u"OptimizedSearch"]
@@ -129,18 +129,13 @@ class TrexMode:
     STL = u"STL"
 
 
     STL = u"STL"
 
 
-# TODO: Pylint says too-many-instance-attributes.
 class TrafficGenerator(AbstractMeasurer):
     """Traffic Generator."""
 
 class TrafficGenerator(AbstractMeasurer):
     """Traffic Generator."""
 
-    # TODO: Remove "trex" from lines which could work with other TGs.
-
     # Use one instance of TrafficGenerator for all tests in test suite
     ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
 
     def __init__(self):
     # Use one instance of TrafficGenerator for all tests in test suite
     ROBOT_LIBRARY_SCOPE = u"TEST SUITE"
 
     def __init__(self):
-        # TODO: Separate into few dataclasses/dicts.
-        #       Pylint dislikes large unstructured state, and it is right.
         self._node = None
         self._mode = None
         # TG interface order mapping
         self._node = None
         self._mode = None
         # TG interface order mapping
@@ -180,7 +175,6 @@ class TrafficGenerator(AbstractMeasurer):
         self.state_timeout = None
         # Transient data needed for async measurements.
         self._xstats = (None, None)
         self.state_timeout = None
         # Transient data needed for async measurements.
         self._xstats = (None, None)
-        # TODO: Rename "xstats" to something opaque, so T-Rex is not privileged?
 
     @property
     def node(self):
 
     @property
     def node(self):
@@ -284,15 +278,12 @@ class TrafficGenerator(AbstractMeasurer):
         else:
             return "none"
 
         else:
             return "none"
 
-    # TODO: pylint disable=too-many-locals.
     def initialize_traffic_generator(
             self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
             tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
             tg_if2_dst_mac=None):
         """TG initialization.
 
     def initialize_traffic_generator(
             self, tg_node, tg_if1, tg_if2, tg_if1_adj_node, tg_if1_adj_if,
             tg_if2_adj_node, tg_if2_adj_if, osi_layer, tg_if1_dst_mac=None,
             tg_if2_dst_mac=None):
         """TG initialization.
 
-        TODO: Document why do we need (and how do we use) _ifaces_reordered.
-
         :param tg_node: Traffic generator node.
         :param tg_if1: TG - name of first interface.
         :param tg_if2: TG - name of second interface.
         :param tg_node: Traffic generator node.
         :param tg_if1: TG - name of first interface.
         :param tg_if2: TG - name of second interface.
@@ -319,87 +310,32 @@ class TrafficGenerator(AbstractMeasurer):
         subtype = check_subtype(tg_node)
         if subtype == NodeSubTypeTG.TREX:
             self._node = tg_node
         subtype = check_subtype(tg_node)
         if subtype == NodeSubTypeTG.TREX:
             self._node = tg_node
-            self._mode = TrexMode.ASTF if osi_layer == u"L7" else TrexMode.STL
-            if1 = dict()
-            if2 = dict()
-            if1[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if1)
-            if2[u"pci"] = Topology().get_interface_pci_addr(self._node, tg_if2)
-            if1[u"addr"] = Topology().get_interface_mac(self._node, tg_if1)
-            if2[u"addr"] = Topology().get_interface_mac(self._node, tg_if2)
-
-            if osi_layer == u"L2":
-                if1[u"adj_addr"] = if2[u"addr"]
-                if2[u"adj_addr"] = if1[u"addr"]
-            elif osi_layer in (u"L3", u"L7"):
-                if1[u"adj_addr"] = Topology().get_interface_mac(
+            self._mode = TrexMode.ASTF if osi_layer == "L7" else TrexMode.STL
+
+            if osi_layer == "L2":
+                tg_if1_adj_addr = Topology().get_interface_mac(tg_node, tg_if2)
+                tg_if2_adj_addr = Topology().get_interface_mac(tg_node, tg_if1)
+            elif osi_layer in ("L3", "L7"):
+                tg_if1_adj_addr = Topology().get_interface_mac(
                     tg_if1_adj_node, tg_if1_adj_if
                 )
                     tg_if1_adj_node, tg_if1_adj_if
                 )
-                if2[u"adj_addr"] = Topology().get_interface_mac(
+                tg_if2_adj_addr = Topology().get_interface_mac(
                     tg_if2_adj_node, tg_if2_adj_if
                 )
             else:
                     tg_if2_adj_node, tg_if2_adj_if
                 )
             else:
-                raise ValueError(u"Unknown OSI layer!")
-
-            # in case of switched environment we can override MAC addresses
-            if tg_if1_dst_mac is not None and tg_if2_dst_mac is not None:
-                if1[u"adj_addr"] = tg_if1_dst_mac
-                if2[u"adj_addr"] = tg_if2_dst_mac
-
-            if min(if1[u"pci"], if2[u"pci"]) != if1[u"pci"]:
-                if1, if2 = if2, if1
+                raise ValueError("Unknown OSI layer!")
+
+            tg_topology = list()
+            tg_topology.append(dict(interface=tg_if1, dst_mac=tg_if1_adj_addr))
+            tg_topology.append(dict(interface=tg_if2, dst_mac=tg_if2_adj_addr))
+            if1_pci = Topology().get_interface_pci_addr(self._node, tg_if1)
+            if2_pci = Topology().get_interface_pci_addr(self._node, tg_if2)
+            if min(if1_pci, if2_pci) != if1_pci:
                 self._ifaces_reordered = True
                 self._ifaces_reordered = True
+                tg_topology.sort(reverse=True)
 
 
-            master_thread_id, latency_thread_id, socket, threads = \
-                CpuUtils.get_affinity_trex(
-                    self._node, tg_if1, tg_if2,
-                    tg_dtc=Constants.TREX_CORE_COUNT)
-
-            if osi_layer in (u"L2", u"L3", u"L7"):
-                exec_cmd_no_error(
-                    self._node,
-                    f"sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
-                    f"- version: 2\n"
-                    f"  c: {len(threads)}\n"
-                    f"  limit_memory: {Constants.TREX_LIMIT_MEMORY}\n"
-                    f"  interfaces: [\"{if1[u'pci']}\",\"{if2[u'pci']}\"]\n"
-                    f"  port_info:\n"
-                    f"      - dest_mac: \'{if1[u'adj_addr']}\'\n"
-                    f"        src_mac: \'{if1[u'addr']}\'\n"
-                    f"      - dest_mac: \'{if2[u'adj_addr']}\'\n"
-                    f"        src_mac: \'{if2[u'addr']}\'\n"
-                    f"  platform :\n"
-                    f"      master_thread_id: {master_thread_id}\n"
-                    f"      latency_thread_id: {latency_thread_id}\n"
-                    f"      dual_if:\n"
-                    f"          - socket: {socket}\n"
-                    f"            threads: {threads}\n"
-                    f"EOF'",
-                    sudo=True, message=u"T-Rex config generation!"
-                )
-
-                if Constants.TREX_RX_DESCRIPTORS_COUNT != 0:
-                    exec_cmd_no_error(
-                        self._node,
-                        f"sh -c 'cat << EOF >> /etc/trex_cfg.yaml\n"
-                        f"  rx_desc: {Constants.TREX_RX_DESCRIPTORS_COUNT}\n"
-                        f"EOF'",
-                        sudo=True, message=u"T-Rex rx_desc modification!"
-                    )
-
-                if Constants.TREX_TX_DESCRIPTORS_COUNT != 0:
-                    exec_cmd_no_error(
-                        self._node,
-                        f"sh -c 'cat << EOF >> /etc/trex_cfg.yaml\n"
-                        f"  tx_desc: {Constants.TREX_TX_DESCRIPTORS_COUNT}\n"
-                        f"EOF'",
-                        sudo=True, message=u"T-Rex tx_desc modification!"
-                    )
-            else:
-                raise ValueError(u"Unknown OSI layer!")
-
-            TrafficGenerator.startup_trex(
-                self._node, osi_layer, subtype=subtype
-            )
+            TrexInitConfig.init_trex_startup_configuration(tg_node, tg_topology)
+            TrafficGenerator.startup_trex(tg_node, osi_layer, subtype=subtype)
 
     @staticmethod
     def startup_trex(tg_node, osi_layer, subtype=None):
 
     @staticmethod
     def startup_trex(tg_node, osi_layer, subtype=None):
@@ -670,8 +606,6 @@ class TrafficGenerator(AbstractMeasurer):
         if not isinstance(duration, (float, int)):
             duration = float(duration)
 
         if not isinstance(duration, (float, int)):
             duration = float(duration)
 
-        # TODO: Refactor the code so duration is computed only once,
-        # and both the initial and the computed durations are logged.
         computed_duration, _ = self._compute_duration(duration, multiplier)
 
         command_line = OptionString().add(u"python3")
         computed_duration, _ = self._compute_duration(duration, multiplier)
 
         command_line = OptionString().add(u"python3")
@@ -783,8 +717,6 @@ class TrafficGenerator(AbstractMeasurer):
         if not isinstance(duration, (float, int)):
             duration = float(duration)
 
         if not isinstance(duration, (float, int)):
             duration = float(duration)
 
-        # TODO: Refactor the code so duration is computed only once,
-        # and both the initial and the computed durations are logged.
         duration, _ = self._compute_duration(duration=duration, multiplier=rate)
 
         command_line = OptionString().add(u"python3")
         duration, _ = self._compute_duration(duration=duration, multiplier=rate)
 
         command_line = OptionString().add(u"python3")
@@ -808,7 +740,6 @@ class TrafficGenerator(AbstractMeasurer):
         command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
         command_line.add_with_value(u"delay", Constants.PERF_TRIAL_STL_DELAY)
 
         command_line.add_if(u"force", Constants.TREX_SEND_FORCE)
         command_line.add_with_value(u"delay", Constants.PERF_TRIAL_STL_DELAY)
 
-        # TODO: This is ugly. Handle parsing better.
         self._start_time = time.monotonic()
         self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
         stdout, _ = exec_cmd_no_error(
         self._start_time = time.monotonic()
         self._rate = float(rate[:-3]) if u"pps" in rate else float(rate)
         stdout, _ = exec_cmd_no_error(
@@ -980,7 +911,6 @@ class TrafficGenerator(AbstractMeasurer):
                 )
             elif u"trex-stl" in self.traffic_profile:
                 unit_rate_str = str(rate) + u"pps"
                 )
             elif u"trex-stl" in self.traffic_profile:
                 unit_rate_str = str(rate) + u"pps"
-                # TODO: Suport transaction_scale et al?
                 self.trex_stl_start_remote_exec(
                     duration, unit_rate_str, async_call
                 )
                 self.trex_stl_start_remote_exec(
                     duration, unit_rate_str, async_call
                 )
@@ -1027,7 +957,6 @@ class TrafficGenerator(AbstractMeasurer):
         complete = False
         if self.ramp_up_rate:
             # Figure out whether we need to insert a ramp-up trial.
         complete = False
         if self.ramp_up_rate:
             # Figure out whether we need to insert a ramp-up trial.
-            # TODO: Give up on async_call=True?
             if ramp_up_only or self.ramp_up_start is None:
                 # We never ramped up yet (at least not in this test case).
                 ramp_up_needed = True
             if ramp_up_only or self.ramp_up_start is None:
                 # We never ramped up yet (at least not in this test case).
                 ramp_up_needed = True
@@ -1099,8 +1028,6 @@ class TrafficGenerator(AbstractMeasurer):
     def fail_if_no_traffic_forwarded(self):
         """Fail if no traffic forwarded.
 
     def fail_if_no_traffic_forwarded(self):
         """Fail if no traffic forwarded.
 
-        TODO: Check number of passed transactions instead.
-
         :returns: nothing
         :raises Exception: If no traffic forwarded.
         """
         :returns: nothing
         :raises Exception: If no traffic forwarded.
         """
@@ -1261,8 +1188,6 @@ class TrafficGenerator(AbstractMeasurer):
         depend on the transaction type. Usually they are in transactions
         per second, or aggregated packets per second.
 
         depend on the transaction type. Usually they are in transactions
         per second, or aggregated packets per second.
 
-        TODO: Fail on running or already reported measurement.
-
         :returns: Structure containing the result of the measurement.
         :rtype: ReceiveRateMeasurement
         """
         :returns: Structure containing the result of the measurement.
         :rtype: ReceiveRateMeasurement
         """
@@ -1405,8 +1330,6 @@ class TrafficGenerator(AbstractMeasurer):
         if self.sleep_till_duration:
             sleeptime = time_stop - time.monotonic()
             if sleeptime > 0.0:
         if self.sleep_till_duration:
             sleeptime = time_stop - time.monotonic()
             if sleeptime > 0.0:
-                # TODO: Sometimes we have time to do additional trials here,
-                # adapt PLRsearch to accept all the results.
                 time.sleep(sleeptime)
         return result
 
                 time.sleep(sleeptime)
         return result
 
@@ -1447,7 +1370,6 @@ class TrafficGenerator(AbstractMeasurer):
         :param transaction_type: An identifier specifying which counters
             and formulas to use when computing attempted and failed
             transactions. Default: "packet".
         :param transaction_type: An identifier specifying which counters
             and formulas to use when computing attempted and failed
             transactions. Default: "packet".
-            TODO: Does this also specify parsing for the measured duration?
         :param duration_limit: Zero or maximum limit for computed (or given)
             duration.
         :param negative_loss: If false, negative loss is reported as zero loss.
         :param duration_limit: Zero or maximum limit for computed (or given)
             duration.
         :param negative_loss: If false, negative loss is reported as zero loss.
@@ -1596,8 +1518,6 @@ class OptimizedSearch:
             u"resources.libraries.python.TrafficGenerator"
         )
         # Overrides for fixed transaction amount.
             u"resources.libraries.python.TrafficGenerator"
         )
         # Overrides for fixed transaction amount.
-        # TODO: Move to robot code? We have two call sites, so this saves space,
-        #       even though this is surprising for log readers.
         if transaction_scale:
             initial_trial_duration = 1.0
             final_trial_duration = 1.0
         if transaction_scale:
             initial_trial_duration = 1.0
             final_trial_duration = 1.0
@@ -1725,11 +1645,7 @@ class OptimizedSearch:
             u"resources.libraries.python.TrafficGenerator"
         )
         # Overrides for fixed transaction amount.
             u"resources.libraries.python.TrafficGenerator"
         )
         # Overrides for fixed transaction amount.
-        # TODO: Move to robot code? We have a single call site
-        #       but MLRsearch has two and we want the two to be used similarly.
         if transaction_scale:
         if transaction_scale:
-            # TODO: What is a good value for max scale?
-            # TODO: Scale the timeout with transaction scale.
             timeout = 7200.0
         tg_instance.set_rate_provider_defaults(
             frame_size=frame_size,
             timeout = 7200.0
         tg_instance.set_rate_provider_defaults(
             frame_size=frame_size,