get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/134580/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 134580,
    "url": "http://patches.dpdk.org/api/patches/134580/?format=api",
    "web_url": "http://patches.dpdk.org/project/dpdk/patch/20231123151344.162812-15-juraj.linkes@pantheon.tech/",
    "project": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/projects/1/?format=api",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "dev.dpdk.org",
        "list_email": "dev@dpdk.org",
        "web_url": "http://core.dpdk.org",
        "scm_url": "git://dpdk.org/dpdk",
        "webscm_url": "http://git.dpdk.org/dpdk",
        "list_archive_url": "https://inbox.dpdk.org/dev",
        "list_archive_url_format": "https://inbox.dpdk.org/dev/{}",
        "commit_url_format": ""
    },
    "msgid": "<20231123151344.162812-15-juraj.linkes@pantheon.tech>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20231123151344.162812-15-juraj.linkes@pantheon.tech",
    "date": "2023-11-23T15:13:37",
    "name": "[v8,14/21] dts: cpu docstring update",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "94cd204bb39e641b62b201c578c1d54aa696864a",
    "submitter": {
        "id": 1626,
        "url": "http://patches.dpdk.org/api/people/1626/?format=api",
        "name": "Juraj Linkeš",
        "email": "juraj.linkes@pantheon.tech"
    },
    "delegate": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "http://patches.dpdk.org/project/dpdk/patch/20231123151344.162812-15-juraj.linkes@pantheon.tech/mbox/",
    "series": [
        {
            "id": 30375,
            "url": "http://patches.dpdk.org/api/series/30375/?format=api",
            "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=30375",
            "date": "2023-11-23T15:13:23",
            "name": "dts: docstrings update",
            "version": 8,
            "mbox": "http://patches.dpdk.org/series/30375/mbox/"
        }
    ],
    "comments": "http://patches.dpdk.org/api/patches/134580/comments/",
    "check": "success",
    "checks": "http://patches.dpdk.org/api/patches/134580/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dev-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 98AD1433AC;\n\tThu, 23 Nov 2023 16:16:02 +0100 (CET)",
            "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id E645042FD9;\n\tThu, 23 Nov 2023 16:14:22 +0100 (CET)",
            "from mail-wr1-f53.google.com (mail-wr1-f53.google.com\n [209.85.221.53]) by mails.dpdk.org (Postfix) with ESMTP id B9E8B42FE7\n for <dev@dpdk.org>; Thu, 23 Nov 2023 16:14:05 +0100 (CET)",
            "by mail-wr1-f53.google.com with SMTP id\n ffacd0b85a97d-3316bd84749so638471f8f.2\n for <dev@dpdk.org>; Thu, 23 Nov 2023 07:14:05 -0800 (PST)",
            "from jlinkes-PT-Latitude-5530.. ([84.245.121.10])\n by smtp.gmail.com with ESMTPSA id\n q4-20020adfea04000000b003296b488961sm1870143wrm.31.2023.11.23.07.14.04\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Thu, 23 Nov 2023 07:14:05 -0800 (PST)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=pantheon.tech; s=google; t=1700752445; x=1701357245; darn=dpdk.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=lDAzXB13bmA5V61UH4hz6KNJGJbhuXFju4FDnIEpq4c=;\n b=BSypjx1JOT1gkqqognG1fTQ8gEItO2k/Njz7AzL9ODbtF6BiJerKtVFLZqMApkTVD1\n DGYPylWBEN9nolkdm6kTQyLxuOPIGUJocD5AIndq6GuvwGaSwLtIkqEOkz1nuLahZ9vA\n ySqwP4MR6Id9knjdDW04R9MrxWLmd1ySYKZwQc0in8BM2YZN4J5GCUctjaFO24SJaImF\n WYELSx4cnRifhneQNqFyX3bh/6V8Fj8c6pRJRDpktBGR4zXmLG8SxGhr9g/ntS4fcKtt\n G9rUGoHXV9Uymo7Hh25KD+RLzeNSlAZvipP3Q3JL/HBxIXJmN1pIEmki7MXjZG/GraNt\n ZbPA==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1700752445; x=1701357245;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=lDAzXB13bmA5V61UH4hz6KNJGJbhuXFju4FDnIEpq4c=;\n b=Hof1DRyGpds1w39kDMTuYnrUzD1j6fBlt50F1b4DCwxspk3lpCox5ouu2FA9ISTqmN\n PSnpDYIRWipF9S4opFfl7FcHJpp5DhNXd+8VSiOIlT774IjV7Ih/hRwAOyozU30LGkuj\n FlQ6WOaY8M17omXYarF/PJ+qFYsh9NklZOlCmSAUYwBtBow5Lu+wX2XVeOWEGjpOsK89\n RgWkvJX6IfUTvlDzr0dVx4TjANVr3r9851HDDWLujCkatfnIGEOqXTWxMDksPU9Nfzl6\n jMYnCQHGePOdKqq99auGVNwMAbQOLHUxtH6Wz/gopp6mbvMwFH+4JVk+aPQkT0+p1pEn\n 2Pjg==",
        "X-Gm-Message-State": "AOJu0YwC6FjEril01OQomiYwLVoDEWlo2Wiy9gMQvUg+i4p1YuRvfCuY\n zBPO0c0odjxBKJgEiOsejumDNA==",
        "X-Google-Smtp-Source": "\n AGHT+IEZ2F9gYo4NvV2y78ZhWSwZBZO3N1ZTkAoCNm30chYS880ScISj6j5qJYsslbEcaesMsQA+Zw==",
        "X-Received": "by 2002:a05:6000:1145:b0:32d:9df1:6f6d with SMTP id\n d5-20020a056000114500b0032d9df16f6dmr4134899wrx.17.1700752445315;\n Thu, 23 Nov 2023 07:14:05 -0800 (PST)",
        "From": "=?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "To": "thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, jspewock@iol.unh.edu,\n probb@iol.unh.edu, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com,\n Luca.Vizzarro@arm.com",
        "Cc": "dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "Subject": "[PATCH v8 14/21] dts: cpu docstring update",
        "Date": "Thu, 23 Nov 2023 16:13:37 +0100",
        "Message-Id": "<20231123151344.162812-15-juraj.linkes@pantheon.tech>",
        "X-Mailer": "git-send-email 2.34.1",
        "In-Reply-To": "<20231123151344.162812-1-juraj.linkes@pantheon.tech>",
        "References": "<20231115130959.39420-1-juraj.linkes@pantheon.tech>\n <20231123151344.162812-1-juraj.linkes@pantheon.tech>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=UTF-8",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "dev@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "List-Post": "<mailto:dev@dpdk.org>",
        "List-Help": "<mailto:dev-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dev-bounces@dpdk.org"
    },
    "content": "Format according to the Google format and PEP257, with slight\ndeviations.\n\nSigned-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>\n---\n dts/framework/testbed_model/cpu.py | 196 +++++++++++++++++++++--------\n 1 file changed, 144 insertions(+), 52 deletions(-)",
    "diff": "diff --git a/dts/framework/testbed_model/cpu.py b/dts/framework/testbed_model/cpu.py\nindex 1b392689f5..9e33b2825d 100644\n--- a/dts/framework/testbed_model/cpu.py\n+++ b/dts/framework/testbed_model/cpu.py\n@@ -1,6 +1,22 @@\n # SPDX-License-Identifier: BSD-3-Clause\n # Copyright(c) 2023 PANTHEON.tech s.r.o.\n \n+\"\"\"CPU core representation and filtering.\n+\n+This module provides a unified representation of logical CPU cores along\n+with filtering capabilities.\n+\n+When symmetric multiprocessing (SMP or multithreading) is enabled on a server,\n+the physical CPU cores are split into logical CPU cores with different IDs.\n+\n+:class:`LogicalCoreCountFilter` filters by the number of logical cores. It's possible to specify\n+the socket from which to filter the number of logical cores. It's also possible to not use all\n+logical CPU cores from each physical core (e.g. only the first logical core of each physical core).\n+\n+:class:`LogicalCoreListFilter` filters by logical core IDs. This mostly checks that\n+the logical cores are actually present on the server.\n+\"\"\"\n+\n import dataclasses\n from abc import ABC, abstractmethod\n from collections.abc import Iterable, ValuesView\n@@ -11,9 +27,17 @@\n \n @dataclass(slots=True, frozen=True)\n class LogicalCore(object):\n-    \"\"\"\n-    Representation of a CPU core. A physical core is represented in OS\n-    by multiple logical cores (lcores) if CPU multithreading is enabled.\n+    \"\"\"Representation of a logical CPU core.\n+\n+    A physical core is represented in OS by multiple logical cores (lcores)\n+    if CPU multithreading is enabled. When multithreading is disabled, their IDs are the same.\n+\n+    Attributes:\n+        lcore: The logical core ID of a CPU core. It's the same as `core` with\n+            disabled multithreading.\n+        core: The physical core ID of a CPU core.\n+        socket: The physical socket ID where the CPU resides.\n+        node: The NUMA node ID where the CPU resides.\n     \"\"\"\n \n     lcore: int\n@@ -22,27 +46,36 @@ class LogicalCore(object):\n     node: int\n \n     def __int__(self) -> int:\n+        \"\"\"The CPU is best represented by the logical core, as that's what we configure in EAL.\"\"\"\n         return self.lcore\n \n \n class LogicalCoreList(object):\n-    \"\"\"\n-    Convert these options into a list of logical core ids.\n-    lcore_list=[LogicalCore1, LogicalCore2] - a list of LogicalCores\n-    lcore_list=[0,1,2,3] - a list of int indices\n-    lcore_list=['0','1','2-3'] - a list of str indices; ranges are supported\n-    lcore_list='0,1,2-3' - a comma delimited str of indices; ranges are supported\n-\n-    The class creates a unified format used across the framework and allows\n-    the user to use either a str representation (using str(instance) or directly\n-    in f-strings) or a list representation (by accessing instance.lcore_list).\n-    Empty lcore_list is allowed.\n+    r\"\"\"A unified way to store :class:`LogicalCore`\\s.\n+\n+    Create a unified format used across the framework and allow the user to use\n+    either a :class:`str` representation (using ``str(instance)`` or directly in f-strings)\n+    or a :class:`list` representation (by accessing the `lcore_list` property,\n+    which stores logical core IDs).\n     \"\"\"\n \n     _lcore_list: list[int]\n     _lcore_str: str\n \n     def __init__(self, lcore_list: list[int] | list[str] | list[LogicalCore] | str):\n+        \"\"\"Process `lcore_list`, then sort.\n+\n+        There are four supported logical core list formats::\n+\n+            lcore_list=[LogicalCore1, LogicalCore2]  # a list of LogicalCores\n+            lcore_list=[0,1,2,3]        # a list of int indices\n+            lcore_list=['0','1','2-3']  # a list of str indices; ranges are supported\n+            lcore_list='0,1,2-3'        # a comma delimited str of indices; ranges are supported\n+\n+        Args:\n+            lcore_list: Various ways to represent multiple logical cores.\n+                Empty `lcore_list` is allowed.\n+        \"\"\"\n         self._lcore_list = []\n         if isinstance(lcore_list, str):\n             lcore_list = lcore_list.split(\",\")\n@@ -58,6 +91,7 @@ def __init__(self, lcore_list: list[int] | list[str] | list[LogicalCore] | str):\n \n     @property\n     def lcore_list(self) -> list[int]:\n+        \"\"\"The logical core IDs.\"\"\"\n         return self._lcore_list\n \n     def _get_consecutive_lcores_range(self, lcore_ids_list: list[int]) -> list[str]:\n@@ -83,28 +117,30 @@ def _get_consecutive_lcores_range(self, lcore_ids_list: list[int]) -> list[str]:\n         return formatted_core_list\n \n     def __str__(self) -> str:\n+        \"\"\"The consecutive ranges of logical core IDs.\"\"\"\n         return self._lcore_str\n \n \n @dataclasses.dataclass(slots=True, frozen=True)\n class LogicalCoreCount(object):\n-    \"\"\"\n-    Define the number of logical cores to use.\n-    If sockets is not None, socket_count is ignored.\n-    \"\"\"\n+    \"\"\"Define the number of logical cores per physical cores per sockets.\"\"\"\n \n+    #: Use this many logical cores per each physical core.\n     lcores_per_core: int = 1\n+    #: Use this many physical cores per each socket.\n     cores_per_socket: int = 2\n+    #: Use this many sockets.\n     socket_count: int = 1\n+    #: Use exactly these sockets. This takes precedence over `socket_count`,\n+    #: so when `sockets` is not :data:`None`, `socket_count` is ignored.\n     sockets: list[int] | None = None\n \n \n class LogicalCoreFilter(ABC):\n-    \"\"\"\n-    Filter according to the input filter specifier. Each filter needs to be\n-    implemented in a derived class.\n-    This class only implements operations common to all filters, such as sorting\n-    the list to be filtered beforehand.\n+    \"\"\"Common filtering class.\n+\n+    Each filter needs to be implemented in a subclass. This base class sorts the list of cores\n+    and defines the filtering method, which must be implemented by subclasses.\n     \"\"\"\n \n     _filter_specifier: LogicalCoreCount | LogicalCoreList\n@@ -116,6 +152,17 @@ def __init__(\n         filter_specifier: LogicalCoreCount | LogicalCoreList,\n         ascending: bool = True,\n     ):\n+        \"\"\"Filter according to the input filter specifier.\n+\n+        The input `lcore_list` is copied and sorted by physical core before filtering.\n+        The list is copied so that the original is left intact.\n+\n+        Args:\n+            lcore_list: The logical CPU cores to filter.\n+            filter_specifier: Filter cores from `lcore_list` according to this filter.\n+            ascending: Sort cores in ascending order (lowest to highest IDs). If data:`False`,\n+                sort in descending order.\n+        \"\"\"\n         self._filter_specifier = filter_specifier\n \n         # sorting by core is needed in case hyperthreading is enabled\n@@ -124,31 +171,45 @@ def __init__(\n \n     @abstractmethod\n     def filter(self) -> list[LogicalCore]:\n-        \"\"\"\n-        Use self._filter_specifier to filter self._lcores_to_filter\n-        and return the list of filtered LogicalCores.\n-        self._lcores_to_filter is a sorted copy of the original list,\n-        so it may be modified.\n+        r\"\"\"Filter the cores.\n+\n+        Use `self._filter_specifier` to filter `self._lcores_to_filter` and return\n+        the filtered :class:`LogicalCore`\\s.\n+        `self._lcores_to_filter` is a sorted copy of the original list, so it may be modified.\n+\n+        Returns:\n+            The filtered cores.\n         \"\"\"\n \n \n class LogicalCoreCountFilter(LogicalCoreFilter):\n-    \"\"\"\n+    \"\"\"Filter cores by specified counts.\n+\n     Filter the input list of LogicalCores according to specified rules:\n-    Use cores from the specified number of sockets or from the specified socket ids.\n-    If sockets is specified, it takes precedence over socket_count.\n-    From each of those sockets, use only cores_per_socket of cores.\n-    And for each core, use lcores_per_core of logical cores. Hypertheading\n-    must be enabled for this to take effect.\n-    If ascending is True, use cores with the lowest numerical id first\n-    and continue in ascending order. If False, start with the highest\n-    id and continue in descending order. This ordering affects which\n-    sockets to consider first as well.\n+\n+        * The input `filter_specifier` is :class:`LogicalCoreCount`,\n+        * Use cores from the specified number of sockets or from the specified socket ids,\n+        * If `sockets` is specified, it takes precedence over `socket_count`,\n+        * From each of those sockets, use only `cores_per_socket` of cores,\n+        * And for each core, use `lcores_per_core` of logical cores. Hypertheading\n+          must be enabled for this to take effect.\n     \"\"\"\n \n     _filter_specifier: LogicalCoreCount\n \n     def filter(self) -> list[LogicalCore]:\n+        \"\"\"Filter the cores according to :class:`LogicalCoreCount`.\n+\n+        Start by filtering the allowed sockets. The cores matching the allowed sockets are returned.\n+        The cores of each socket are stored in separate lists.\n+\n+        Then filter the allowed physical cores from those lists of cores per socket. When filtering\n+        physical cores, store the desired number of logical cores per physical core which then\n+        together constitute the final filtered list.\n+\n+        Returns:\n+            The filtered cores.\n+        \"\"\"\n         sockets_to_filter = self._filter_sockets(self._lcores_to_filter)\n         filtered_lcores = []\n         for socket_to_filter in sockets_to_filter:\n@@ -158,24 +219,37 @@ def filter(self) -> list[LogicalCore]:\n     def _filter_sockets(\n         self, lcores_to_filter: Iterable[LogicalCore]\n     ) -> ValuesView[list[LogicalCore]]:\n-        \"\"\"\n-        Remove all lcores that don't match the specified socket(s).\n-        If self._filter_specifier.sockets is not None, keep lcores from those sockets,\n-        otherwise keep lcores from the first\n-        self._filter_specifier.socket_count sockets.\n+        \"\"\"Filter a list of cores per each allowed socket.\n+\n+        The sockets may be specified in two ways, either a number or a specific list of sockets.\n+        In case of a specific list, we just need to return the cores from those sockets.\n+        If filtering a number of cores, we need to go through all cores and note which sockets\n+        appear and only filter from the first n that appear.\n+\n+        Args:\n+            lcores_to_filter: The cores to filter. These must be sorted by the physical core.\n+\n+        Returns:\n+            A list of lists of logical CPU cores. Each list contains cores from one socket.\n         \"\"\"\n         allowed_sockets: set[int] = set()\n         socket_count = self._filter_specifier.socket_count\n         if self._filter_specifier.sockets:\n+            # when sockets in filter is specified, the sockets are already set\n             socket_count = len(self._filter_specifier.sockets)\n             allowed_sockets = set(self._filter_specifier.sockets)\n \n+        # filter socket_count sockets from all sockets by checking the socket of each CPU\n         filtered_lcores: dict[int, list[LogicalCore]] = {}\n         for lcore in lcores_to_filter:\n             if not self._filter_specifier.sockets:\n+                # this is when sockets is not set, so we do the actual filtering\n+                # when it is set, allowed_sockets is already defined and can't be changed\n                 if len(allowed_sockets) < socket_count:\n+                    # allowed_sockets is a set, so adding an existing socket won't re-add it\n                     allowed_sockets.add(lcore.socket)\n             if lcore.socket in allowed_sockets:\n+                # separate lcores into sockets; this makes it easier in further processing\n                 if lcore.socket in filtered_lcores:\n                     filtered_lcores[lcore.socket].append(lcore)\n                 else:\n@@ -192,12 +266,13 @@ def _filter_sockets(\n     def _filter_cores_from_socket(\n         self, lcores_to_filter: Iterable[LogicalCore]\n     ) -> list[LogicalCore]:\n-        \"\"\"\n-        Keep only the first self._filter_specifier.cores_per_socket cores.\n-        In multithreaded environments, keep only\n-        the first self._filter_specifier.lcores_per_core lcores of those cores.\n-        \"\"\"\n+        \"\"\"Filter a list of cores from the given socket.\n+\n+        Go through the cores and note how many logical cores per physical core have been filtered.\n \n+        Returns:\n+            The filtered logical CPU cores.\n+        \"\"\"\n         # no need to use ordered dict, from Python3.7 the dict\n         # insertion order is preserved (LIFO).\n         lcore_count_per_core_map: dict[int, int] = {}\n@@ -238,15 +313,21 @@ def _filter_cores_from_socket(\n \n \n class LogicalCoreListFilter(LogicalCoreFilter):\n-    \"\"\"\n-    Filter the input list of Logical Cores according to the input list of\n-    lcore indices.\n-    An empty LogicalCoreList won't filter anything.\n+    \"\"\"Filter the logical CPU cores by logical CPU core IDs.\n+\n+    This is a simple filter that looks at logical CPU IDs and only filter those that match.\n+\n+    The input filter is :class:`LogicalCoreList`. An empty LogicalCoreList won't filter anything.\n     \"\"\"\n \n     _filter_specifier: LogicalCoreList\n \n     def filter(self) -> list[LogicalCore]:\n+        \"\"\"Filter based on logical CPU core ID.\n+\n+        Return:\n+            The filtered logical CPU cores.\n+        \"\"\"\n         if not len(self._filter_specifier.lcore_list):\n             return self._lcores_to_filter\n \n@@ -269,6 +350,17 @@ def lcore_filter(\n     filter_specifier: LogicalCoreCount | LogicalCoreList,\n     ascending: bool,\n ) -> LogicalCoreFilter:\n+    \"\"\"Factory for providing the filter that corresponds to `filter_specifier`.\n+\n+    Args:\n+        core_list: The logical CPU cores to filter.\n+        filter_specifier: The filter to use.\n+        ascending: Sort cores in ascending order (lowest to highest IDs). If :data:`False`,\n+            sort in descending order.\n+\n+    Returns:\n+        The filter that corresponds to `filter_specifier`.\n+    \"\"\"\n     if isinstance(filter_specifier, LogicalCoreList):\n         return LogicalCoreListFilter(core_list, filter_specifier, ascending)\n     elif isinstance(filter_specifier, LogicalCoreCount):\n",
    "prefixes": [
        "v8",
        "14/21"
    ]
}