From: Dean Marx <dmarx@iol.unh.edu>
To: "Tomáš Ďurovec" <tomas.durovec@pantheon.tech>,
"Luca Vizzarro" <luca.vizzarro@arm.com>
Cc: dev@dpdk.org
Subject: Re: [PATCH] dts: improve statistics
Date: Mon, 4 Nov 2024 13:49:11 -0500 [thread overview]
Message-ID: <CABD7UXPb4SBWF+_TfX6q_tRfgXCjqUmpSkbwJo1z6gtWMWLLQg@mail.gmail.com> (raw)
In-Reply-To: <CABD7UXOGNLN0s-JYj14P+nePxTkB5fCJp6BFhP0jZGLgnFH2Kg@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 26077 bytes --]
Adding Luca to this thread because it applies to him as well:
Hi Tomáš,
This all looks great, one thing I did notice when running locally is that
the "test_suites" section of results.json contains null values instead of
the names of the suites that were run. Could just be something strange
happening on my side, but I would double check on your end just in case.
Otherwise:
Reviewed-by: Dean Marx <dmarx@iol.unh.edu>
On Fri, Oct 25, 2024 at 1:59 PM Dean Marx <dmarx@iol.unh.edu> wrote:
> Hi Tomáš,
>
> This all looks great, one thing I did notice when running locally is that
> the "test_suites" section of results.json contains null values instead of
> the names of the suites that were run. Could just be something strange
> happening on my side, but I would double check on your end just in case.
> Otherwise:
>
> Reviewed-by: Dean Marx <dmarx@iol.unh.edu>
>
> On Mon, Sep 30, 2024 at 12:26 PM Tomáš Ďurovec <tomas.durovec@pantheon.tech>
> wrote:
>
>> The previous version of statistics store only the last test run
>> attribute and result. In this patch we are adding header for each
>> test run and overall summary of test runs at the end. This is
>> represented as textual summaries with basic information and json
>> result with more detailed information.
>>
>> Signed-off-by: Tomáš Ďurovec <tomas.durovec@pantheon.tech>
>>
>> Depends-on: series-33184 ("DTS external DPDK build")
>> ---
>> dts/framework/runner.py | 7 +-
>> dts/framework/test_result.py | 409 ++++++++++++++++++++++++++++-------
>> 2 files changed, 332 insertions(+), 84 deletions(-)
>>
>> diff --git a/dts/framework/runner.py b/dts/framework/runner.py
>> index 7d463c1fa1..be615ccace 100644
>> --- a/dts/framework/runner.py
>> +++ b/dts/framework/runner.py
>> @@ -84,7 +84,7 @@ def __init__(self):
>> if not os.path.exists(SETTINGS.output_dir):
>> os.makedirs(SETTINGS.output_dir)
>> self._logger.add_dts_root_logger_handlers(SETTINGS.verbose,
>> SETTINGS.output_dir)
>> - self._result = DTSResult(self._logger)
>> + self._result = DTSResult(SETTINGS.output_dir, self._logger)
>> self._test_suite_class_prefix = "Test"
>> self._test_suite_module_prefix = "tests.TestSuite_"
>> self._func_test_case_regex = r"test_(?!perf_)"
>> @@ -421,11 +421,12 @@ def _run_test_run(
>> self._logger.info(
>> f"Running test run with SUT '{
>> test_run_config.system_under_test_node.name}'."
>> )
>> - test_run_result.add_sut_info(sut_node.node_info)
>> + test_run_result.ports = sut_node.ports
>> + test_run_result.sut_info = sut_node.node_info
>> try:
>> dpdk_location = SETTINGS.dpdk_location or
>> test_run_config.dpdk_config.dpdk_location
>> sut_node.set_up_test_run(test_run_config, dpdk_location)
>> -
>> test_run_result.add_dpdk_build_info(sut_node.get_dpdk_build_info())
>> + test_run_result.dpdk_build_info =
>> sut_node.get_dpdk_build_info()
>> tg_node.set_up_test_run(test_run_config, dpdk_location)
>> test_run_result.update_setup(Result.PASS)
>> except Exception as e:
>> diff --git a/dts/framework/test_result.py b/dts/framework/test_result.py
>> index 0a10723098..bf148a6b45 100644
>> --- a/dts/framework/test_result.py
>> +++ b/dts/framework/test_result.py
>> @@ -22,18 +22,19 @@
>> variable modify the directory where the files with results will be
>> stored.
>> """
>>
>> -import os.path
>> +import json
>> from collections.abc import MutableSequence
>> -from dataclasses import dataclass
>> +from dataclasses import asdict, dataclass
>> from enum import Enum, auto
>> +from pathlib import Path
>> from types import FunctionType
>> -from typing import Union
>> +from typing import Any, Callable, TypedDict
>>
>> from .config import DPDKBuildInfo, NodeInfo, TestRunConfiguration,
>> TestSuiteConfig
>> from .exception import DTSError, ErrorSeverity
>> from .logger import DTSLogger
>> -from .settings import SETTINGS
>> from .test_suite import TestSuite
>> +from .testbed_model.port import Port
>>
>>
>> @dataclass(slots=True, frozen=True)
>> @@ -85,6 +86,60 @@ def __bool__(self) -> bool:
>> return self is self.PASS
>>
>>
>> +class TestCaseResultDict(TypedDict):
>> + """Represents the `TestCaseResult` results.
>> +
>> + Attributes:
>> + test_case_name: The name of the test case.
>> + result: The result name of the test case.
>> + """
>> +
>> + test_case_name: str
>> + result: str
>> +
>> +
>> +class TestSuiteResultDict(TypedDict):
>> + """Represents the `TestSuiteResult` results.
>> +
>> + Attributes:
>> + test_suite_name: The name of the test suite.
>> + test_cases: A list of test case results contained in this test
>> suite.
>> + """
>> +
>> + test_suite_name: str
>> + test_cases: list[TestCaseResultDict]
>> +
>> +
>> +class TestRunResultDict(TypedDict, total=False):
>> + """Represents the `TestRunResult` results.
>> +
>> + Attributes:
>> + compiler_version: The version of the compiler used for the DPDK
>> build.
>> + dpdk_version: The version of DPDK being tested.
>> + ports: A list of ports associated with the test run.
>> + test_suites: A list of test suite results included in this test
>> run.
>> + summary: A dictionary containing overall results, such as
>> pass/fail counts.
>> + """
>> +
>> + compiler_version: str | None
>> + dpdk_version: str | None
>> + ports: list[dict[str, Any]]
>> + test_suites: list[TestSuiteResultDict]
>> + summary: dict[str, int | float]
>> +
>> +
>> +class DtsRunResultDict(TypedDict):
>> + """Represents the `DtsRunResult` results.
>> +
>> + Attributes:
>> + test_runs: A list of test run results.
>> + summary: A summary dictionary containing overall statistics for
>> the test runs.
>> + """
>> +
>> + test_runs: list[TestRunResultDict]
>> + summary: dict[str, int | float]
>> +
>> +
>> class FixtureResult:
>> """A record that stores the result of a setup or a teardown.
>>
>> @@ -198,14 +253,34 @@ def get_errors(self) -> list[Exception]:
>> """
>> return self._get_setup_teardown_errors() +
>> self._get_child_errors()
>>
>> - def add_stats(self, statistics: "Statistics") -> None:
>> - """Collate stats from the whole result hierarchy.
>> + def to_dict(self):
>> + """Convert the results hierarchy into a dictionary
>> representation."""
>> +
>> + def add_result(self, results: dict[str, int]):
>> + """Collate the test case result from the result hierarchy.
>>
>> Args:
>> - statistics: The :class:`Statistics` object where the stats
>> will be collated.
>> + results: The dictionary to which results will be collated.
>> """
>> for child_result in self.child_results:
>> - child_result.add_stats(statistics)
>> + child_result.add_result(results)
>> +
>> + def generate_pass_rate_dict(self, test_run_summary) -> dict[str,
>> float]:
>> + """Generate a dictionary with the FAIL/PASS ratio of all test
>> cases.
>> +
>> + Args:
>> + test_run_summary: The summary dictionary containing test
>> result counts.
>> +
>> + Returns:
>> + A dictionary with the FAIL/PASS ratio of all test cases.
>> + """
>> + return {
>> + "PASS_RATE": (
>> + float(test_run_summary[Result.PASS.name])
>> + * 100
>> + / sum(test_run_summary[result.name] for result in
>> Result)
>> + )
>> + }
>>
>>
>> class DTSResult(BaseResult):
>> @@ -220,31 +295,25 @@ class DTSResult(BaseResult):
>> and as such is where the data form the whole hierarchy is collated
>> or processed.
>>
>> The internal list stores the results of all test runs.
>> -
>> - Attributes:
>> - dpdk_version: The DPDK version to record.
>> """
>>
>> - dpdk_version: str | None
>> + _output_dir: str
>> _logger: DTSLogger
>> _errors: list[Exception]
>> _return_code: ErrorSeverity
>> - _stats_result: Union["Statistics", None]
>> - _stats_filename: str
>>
>> - def __init__(self, logger: DTSLogger):
>> + def __init__(self, output_dir: str, logger: DTSLogger):
>> """Extend the constructor with top-level specifics.
>>
>> Args:
>> + output_dir: The directory where DTS logs and results are
>> saved.
>> logger: The logger instance the whole result will use.
>> """
>> super().__init__()
>> - self.dpdk_version = None
>> + self._output_dir = output_dir
>> self._logger = logger
>> self._errors = []
>> self._return_code = ErrorSeverity.NO_ERR
>> - self._stats_result = None
>> - self._stats_filename = os.path.join(SETTINGS.output_dir,
>> "statistics.txt")
>>
>> def add_test_run(self, test_run_config: TestRunConfiguration) ->
>> "TestRunResult":
>> """Add and return the child result (test run).
>> @@ -281,10 +350,8 @@ def process(self) -> None:
>> for error in self._errors:
>> self._logger.debug(repr(error))
>>
>> - self._stats_result = Statistics(self.dpdk_version)
>> - self.add_stats(self._stats_result)
>> - with open(self._stats_filename, "w+") as stats_file:
>> - stats_file.write(str(self._stats_result))
>> + TextSummary(self).save(Path(self._output_dir,
>> "results_summary.txt"))
>> + JsonResults(self).save(Path(self._output_dir, "results.json"))
>>
>> def get_return_code(self) -> int:
>> """Go through all stored Exceptions and return the final DTS
>> error code.
>> @@ -302,6 +369,37 @@ def get_return_code(self) -> int:
>>
>> return int(self._return_code)
>>
>> + def to_dict(self) -> DtsRunResultDict:
>> + """Convert DTS result into a dictionary format.
>> +
>> + The dictionary contains test runs and summary of test runs.
>> +
>> + Returns:
>> + A dictionary representation of the DTS result
>> + """
>> +
>> + def merge_test_run_summaries(test_run_summaries: list[dict[str,
>> int]]) -> dict[str, int]:
>> + """Merge multiple test run summaries into one dictionary.
>> +
>> + Args:
>> + test_run_summaries: List of test run summary
>> dictionaries.
>> +
>> + Returns:
>> + A merged dictionary containing the aggregated summary.
>> + """
>> + return {
>> + key.name: sum(test_run_summary[key.name] for
>> test_run_summary in test_run_summaries)
>> + for key in Result
>> + }
>> +
>> + test_runs = [child.to_dict() for child in self.child_results]
>> + test_run_summary = merge_test_run_summaries([test_run["summary"]
>> for test_run in test_runs])
>> +
>> + return {
>> + "test_runs": test_runs,
>> + "summary": test_run_summary |
>> self.generate_pass_rate_dict(test_run_summary),
>> + }
>> +
>>
>> class TestRunResult(BaseResult):
>> """The test run specific result.
>> @@ -316,13 +414,11 @@ class TestRunResult(BaseResult):
>> sut_kernel_version: The operating system kernel version of the
>> SUT node.
>> """
>>
>> - compiler_version: str | None
>> - dpdk_version: str | None
>> - sut_os_name: str
>> - sut_os_version: str
>> - sut_kernel_version: str
>> _config: TestRunConfiguration
>> _test_suites_with_cases: list[TestSuiteWithCases]
>> + _ports: list[Port]
>> + _sut_info: NodeInfo | None
>> + _dpdk_build_info: DPDKBuildInfo | None
>>
>> def __init__(self, test_run_config: TestRunConfiguration):
>> """Extend the constructor with the test run's config.
>> @@ -331,10 +427,11 @@ def __init__(self, test_run_config:
>> TestRunConfiguration):
>> test_run_config: A test run configuration.
>> """
>> super().__init__()
>> - self.compiler_version = None
>> - self.dpdk_version = None
>> self._config = test_run_config
>> self._test_suites_with_cases = []
>> + self._ports = []
>> + self._sut_info = None
>> + self._dpdk_build_info = None
>>
>> def add_test_suite(
>> self,
>> @@ -374,24 +471,96 @@ def test_suites_with_cases(self,
>> test_suites_with_cases: list[TestSuiteWithCases
>> )
>> self._test_suites_with_cases = test_suites_with_cases
>>
>> - def add_sut_info(self, sut_info: NodeInfo) -> None:
>> - """Add SUT information gathered at runtime.
>> + @property
>> + def ports(self) -> list[Port]:
>> + """Get the list of ports associated with this test run."""
>> + return self._ports
>> +
>> + @ports.setter
>> + def ports(self, ports: list[Port]) -> None:
>> + """Set the list of ports associated with this test run.
>> +
>> + Args:
>> + ports: The list of ports to associate with this test run.
>> +
>> + Raises:
>> + ValueError: If the ports have already been assigned to this
>> test run.
>> + """
>> + if self._ports:
>> + raise ValueError(
>> + "Attempted to assign `ports` to a test run result which
>> already has `ports`."
>> + )
>> + self._ports = ports
>> +
>> + @property
>> + def sut_info(self) -> NodeInfo | None:
>> + """Get the SUT node information associated with this test run."""
>> + return self._sut_info
>> +
>> + @sut_info.setter
>> + def sut_info(self, sut_info: NodeInfo) -> None:
>> + """Set the SUT node information associated with this test run.
>>
>> Args:
>> - sut_info: The additional SUT node information.
>> + sut_info: The SUT node information to associate with this
>> test run.
>> +
>> + Raises:
>> + ValueError: If the SUT information has already been assigned
>> to this test run.
>> """
>> - self.sut_os_name = sut_info.os_name
>> - self.sut_os_version = sut_info.os_version
>> - self.sut_kernel_version = sut_info.kernel_version
>> + if self._sut_info:
>> + raise ValueError(
>> + "Attempted to assign `sut_info` to a test run result
>> which already has `sut_info`."
>> + )
>> + self._sut_info = sut_info
>>
>> - def add_dpdk_build_info(self, versions: DPDKBuildInfo) -> None:
>> - """Add information about the DPDK build gathered at runtime.
>> + @property
>> + def dpdk_build_info(self) -> DPDKBuildInfo | None:
>> + """Get the DPDK build information associated with this test
>> run."""
>> + return self._dpdk_build_info
>> +
>> + @dpdk_build_info.setter
>> + def dpdk_build_info(self, dpdk_build_info: DPDKBuildInfo) -> None:
>> + """Set the DPDK build information associated with this test run.
>>
>> Args:
>> - versions: The additional information.
>> + dpdk_build_info: The DPDK build information to associate
>> with this test run.
>> +
>> + Raises:
>> + ValueError: If the DPDK build information has already been
>> assigned to this test run.
>> """
>> - self.compiler_version = versions.compiler_version
>> - self.dpdk_version = versions.dpdk_version
>> + if self._dpdk_build_info:
>> + raise ValueError(
>> + "Attempted to assign `dpdk_build_info` to a test run
>> result which already "
>> + "has `dpdk_build_info`."
>> + )
>> + self._dpdk_build_info = dpdk_build_info
>> +
>> + def to_dict(self) -> TestRunResultDict:
>> + """Convert the test run result into a dictionary.
>> +
>> + The dictionary contains test suites in this test run, and a
>> summary of the test run and
>> + information about the DPDK version, compiler version and
>> associated ports.
>> +
>> + Returns:
>> + TestRunResultDict: A dictionary representation of the test
>> run result.
>> + """
>> + results = {result.name: 0 for result in Result}
>> + self.add_result(results)
>> +
>> + compiler_version = None
>> + dpdk_version = None
>> +
>> + if self.dpdk_build_info:
>> + compiler_version = self.dpdk_build_info.compiler_version
>> + dpdk_version = self.dpdk_build_info.dpdk_version
>> +
>> + return {
>> + "compiler_version": compiler_version,
>> + "dpdk_version": dpdk_version,
>> + "ports": [asdict(port) for port in self.ports],
>> + "test_suites": [child.to_dict() for child in
>> self.child_results],
>> + "summary": results | self.generate_pass_rate_dict(results),
>> + }
>>
>> def _block_result(self) -> None:
>> r"""Mark the result as :attr:`~Result.BLOCK`\ed."""
>> @@ -436,6 +605,16 @@ def add_test_case(self, test_case_name: str) ->
>> "TestCaseResult":
>> self.child_results.append(result)
>> return result
>>
>> + def to_dict(self) -> TestSuiteResultDict:
>> + """Convert the test suite result into a dictionary.
>> +
>> + The dictionary contains a test suite name and test cases given
>> in this test suite.
>> + """
>> + return {
>> + "test_suite_name": self.test_suite_name,
>> + "test_cases": [child.to_dict() for child in
>> self.child_results],
>> + }
>> +
>> def _block_result(self) -> None:
>> r"""Mark the result as :attr:`~Result.BLOCK`\ed."""
>> for test_case_method in self._test_suite_with_cases.test_cases:
>> @@ -483,16 +662,23 @@ def _get_child_errors(self) -> list[Exception]:
>> return [self.error]
>> return []
>>
>> - def add_stats(self, statistics: "Statistics") -> None:
>> - r"""Add the test case result to statistics.
>> + def to_dict(self) -> TestCaseResultDict:
>> + """Convert the test case result into a dictionary.
>> +
>> + The dictionary contains a test case name and the result name.
>> + """
>> + return {"test_case_name": self.test_case_name, "result":
>> self.result.name}
>> +
>> + def add_result(self, results: dict[str, int]):
>> + r"""Add the test case result to the results.
>>
>> The base method goes through the hierarchy recursively and this
>> method is here to stop
>> - the recursion, as the :class:`TestCaseResult`\s are the leaves
>> of the hierarchy tree.
>> + the recursion, as the :class:`TestCaseResult` are the leaves of
>> the hierarchy tree.
>>
>> Args:
>> - statistics: The :class:`Statistics` object where the stats
>> will be added.
>> + results: The dictionary to which results will be collated.
>> """
>> - statistics += self.result
>> + results[self.result.name] += 1
>>
>> def _block_result(self) -> None:
>> r"""Mark the result as :attr:`~Result.BLOCK`\ed."""
>> @@ -503,53 +689,114 @@ def __bool__(self) -> bool:
>> return bool(self.setup_result) and bool(self.teardown_result)
>> and bool(self.result)
>>
>>
>> -class Statistics(dict):
>> - """How many test cases ended in which result state along some other
>> basic information.
>> +class TextSummary:
>> + """Generates and saves textual summaries of DTS run results.
>>
>> - Subclassing :class:`dict` provides a convenient way to format the
>> data.
>> + The summary includes:
>> + * Results of test run test cases,
>> + * Compiler version of the DPDK build,
>> + * DPDK version of the DPDK source tree,
>> + * Overall summary of results when multiple test runs are present.
>> + """
>>
>> - The data are stored in the following keys:
>> + _dict_result: DtsRunResultDict
>> + _summary: dict[str, int | float]
>> + _text: str
>>
>> - * **PASS RATE** (:class:`int`) -- The FAIL/PASS ratio of all test
>> cases.
>> - * **DPDK VERSION** (:class:`str`) -- The tested DPDK version.
>> - """
>> + def __init__(self, dts_run_result: DTSResult):
>> + """Initializes with a DTSResult object and converts it to a
>> dictionary format.
>>
>> - def __init__(self, dpdk_version: str | None):
>> - """Extend the constructor with keys in which the data are stored.
>> + Args:
>> + dts_run_result: The DTS result.
>> + """
>> + self._dict_result = dts_run_result.to_dict()
>> + self._summary = self._dict_result["summary"]
>> + self._text = ""
>> +
>> + @property
>> + def _outdent(self) -> str:
>> + """Appropriate indentation based on multiple test run results."""
>> + return "\t" if len(self._dict_result["test_runs"]) > 1 else ""
>> +
>> + def save(self, output_path: Path):
>> + """Generate and save text statistics to a file.
>>
>> Args:
>> - dpdk_version: The version of tested DPDK.
>> + output_path: The path where the text file will be saved.
>> """
>> - super().__init__()
>> - for result in Result:
>> - self[result.name] = 0
>> - self["PASS RATE"] = 0.0
>> - self["DPDK VERSION"] = dpdk_version
>> + if self._dict_result["test_runs"]:
>> + with open(f"{output_path}", "w") as fp:
>> +
>> self._add_test_runs_dict_decorator(self._add_test_run_dict)
>> + fp.write(self._text)
>>
>> - def __iadd__(self, other: Result) -> "Statistics":
>> - """Add a Result to the final count.
>> + def _add_test_runs_dict_decorator(self, func: Callable):
>> + """Handles multiple test runs and appends results to the summary.
>>
>> - Example:
>> - stats: Statistics = Statistics() # empty Statistics
>> - stats += Result.PASS # add a Result to `stats`
>> + Adds headers for each test run and overall result when multiple
>> + test runs are provided.
>>
>> Args:
>> - other: The Result to add to this statistics object.
>> + func: Function to process and add results from each test run.
>> + """
>> + if len(self._dict_result["test_runs"]) > 1:
>> + for idx, test_run_result in
>> enumerate(self._dict_result["test_runs"]):
>> + self._text += f"TEST_RUN_{idx}\n"
>> + func(test_run_result)
>>
>> - Returns:
>> - The modified statistics object.
>> + self._add_overall_results()
>> + else:
>> + func(self._dict_result["test_runs"][0])
>> +
>> + def _add_test_run_dict(self, test_run_dict: TestRunResultDict):
>> + """Adds the results and the test run attributes of a single test
>> run to the summary.
>> +
>> + Args:
>> + test_run_dict: Dictionary containing the test run results.
>> """
>> - self[other.name] += 1
>> - self["PASS RATE"] = (
>> - float(self[Result.PASS.name]) * 100 / sum(self[result.name]
>> for result in Result)
>> + self._add_column(
>> + DPDK_VERSION=test_run_dict["dpdk_version"],
>> + COMPILER_VERSION=test_run_dict["compiler_version"],
>> + **test_run_dict["summary"],
>> )
>> - return self
>> -
>> - def __str__(self) -> str:
>> - """Each line contains the formatted key = value pair."""
>> - stats_str = ""
>> - for key, value in self.items():
>> - stats_str += f"{key:<12} = {value}\n"
>> - # according to docs, we should use \n when writing to text
>> files
>> - # on all platforms
>> - return stats_str
>> + self._text += "\n"
>> +
>> + def _add_column(self, **rows):
>> + """Formats and adds key-value pairs to the summary text.
>> +
>> + Handles cases where values might be None by replacing them with
>> "N/A".
>> +
>> + Args:
>> + **rows: Arbitrary key-value pairs representing the result
>> data.
>> + """
>> + rows = {k: "N/A" if v is None else v for k, v in rows.items()}
>> + max_length = len(max(rows, key=len))
>> + for key, value in rows.items():
>> + self._text += f"{self._outdent}{key:<{max_length}} =
>> {value}\n"
>> +
>> + def _add_overall_results(self):
>> + """Add overall summary of test runs."""
>> + self._text += "OVERALL\n"
>> + self._add_column(**self._summary)
>> +
>> +
>> +class JsonResults:
>> + """Save DTS run result in JSON format."""
>> +
>> + _dict_result: DtsRunResultDict
>> +
>> + def __init__(self, dts_run_result: DTSResult):
>> + """Initializes with a DTSResult object and converts it to a
>> dictionary format.
>> +
>> + Args:
>> + dts_run_result: The DTS result.
>> + """
>> + self._dict_result = dts_run_result.to_dict()
>> +
>> + def save(self, output_path: Path):
>> + """Save the result to a file as JSON.
>> +
>> + Args:
>> + output_path: The path where the JSON file will be saved.
>> + """
>> + with open(f"{output_path}", "w") as fp:
>> + json.dump(self._dict_result, fp, indent=4)
>> --
>> 2.46.1
>>
>>
[-- Attachment #2: Type: text/html, Size: 31918 bytes --]
next prev parent reply other threads:[~2024-11-04 18:49 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-09-30 16:26 Tomáš Ďurovec
2024-10-25 17:59 ` Dean Marx
2024-11-04 18:49 ` Dean Marx [this message]
2024-11-06 18:57 ` Nicholas Pratte
-- strict thread matches above, loose matches on Subject: below --
2024-09-28 11:44 Tomáš Ďurovec
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CABD7UXPb4SBWF+_TfX6q_tRfgXCjqUmpSkbwJo1z6gtWMWLLQg@mail.gmail.com \
--to=dmarx@iol.unh.edu \
--cc=dev@dpdk.org \
--cc=luca.vizzarro@arm.com \
--cc=tomas.durovec@pantheon.tech \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).