diff mbox series

[ovs-dev,v5,13/13] documentation: Document ovs-flowviz.

Message ID 20240710170504.2162803-14-amorenoz@redhat.com
State Changes Requested
Headers show
Series Add flow visualization utility. | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed

Commit Message

Adrián Moreno July 10, 2024, 5:05 p.m. UTC
Add a man page for ovs-flowviz as well as a topic page with some more
detailed examples.

Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 Documentation/automake.mk                   |   4 +-
 Documentation/conf.py                       |   2 +
 Documentation/ref/index.rst                 |   1 +
 Documentation/ref/ovs-flowviz.8.rst         | 535 ++++++++++++++++++++
 Documentation/topics/flow-visualization.rst | 314 ++++++++++++
 Documentation/topics/index.rst              |   1 +
 rhel/openvswitch-fedora.spec.in             |   1 +
 rhel/openvswitch.spec.in                    |   1 +
 8 files changed, 858 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/ref/ovs-flowviz.8.rst
 create mode 100644 Documentation/topics/flow-visualization.rst

Comments

Eelco Chaudron Aug. 16, 2024, 1:26 p.m. UTC | #1
On 10 Jul 2024, at 19:05, Adrian Moreno wrote:

> Add a man page for ovs-flowviz as well as a topic page with some more
> detailed examples.
>
> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>


Thanks for sending the v5. See some spelling suggestions below. Included Simon as a more native speaker for any more suggestions ;)

//Eelco

> ---
>  Documentation/automake.mk                   |   4 +-
>  Documentation/conf.py                       |   2 +
>  Documentation/ref/index.rst                 |   1 +
>  Documentation/ref/ovs-flowviz.8.rst         | 535 ++++++++++++++++++++
>  Documentation/topics/flow-visualization.rst | 314 ++++++++++++
>  Documentation/topics/index.rst              |   1 +
>  rhel/openvswitch-fedora.spec.in             |   1 +
>  rhel/openvswitch.spec.in                    |   1 +
>  8 files changed, 858 insertions(+), 1 deletion(-)
>  create mode 100644 Documentation/ref/ovs-flowviz.8.rst
>  create mode 100644 Documentation/topics/flow-visualization.rst
>
> diff --git a/Documentation/automake.mk b/Documentation/automake.mk
> index 47d2e336a..539870aa2 100644
> --- a/Documentation/automake.mk
> +++ b/Documentation/automake.mk
> @@ -45,7 +45,7 @@ DOC_SOURCE = \
>  	Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
>  	Documentation/topics/fuzzing/ovs-fuzzers.rst \
>  	Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
> -	Documentation/topics/testing.rst \
> +	Documentation/topics/flow-visualization.rst \
>  	Documentation/topics/integration.rst \
>  	Documentation/topics/language-bindings.rst \
>  	Documentation/topics/networking-namespaces.rst \
> @@ -55,6 +55,7 @@ DOC_SOURCE = \
>  	Documentation/topics/ovsdb-replication.rst \
>  	Documentation/topics/porting.rst \
>  	Documentation/topics/record-replay.rst \
> +	Documentation/topics/testing.rst \
>  	Documentation/topics/tracing.rst \
>  	Documentation/topics/usdt-probes.rst \
>  	Documentation/topics/userspace-checksum-offloading.rst \
> @@ -162,6 +163,7 @@ RST_MANPAGES = \
>  	ovs-actions.7.rst \
>  	ovs-appctl.8.rst \
>  	ovs-ctl.8.rst \
> +	ovs-flowviz.8.rst \
>  	ovs-l3ping.8.rst \
>  	ovs-parse-backtrace.8.rst \
>  	ovs-pki.8.rst \
> diff --git a/Documentation/conf.py b/Documentation/conf.py
> index 15785605a..3a82f23a7 100644
> --- a/Documentation/conf.py
> +++ b/Documentation/conf.py
> @@ -120,6 +120,8 @@ _man_pages = [
>       u'utility for configuring running Open vSwitch daemons'),
>      ('ovs-ctl.8',
>       u'OVS startup helper script'),
> +    ('ovs-flowviz.8',
> +     u'utility for visualizing OpenFlow and datapath flows'),
>      ('ovs-l3ping.8',
>       u'check network deployment for L3 tunneling problems'),
>      ('ovs-parse-backtrace.8',
> diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
> index 03ada932f..7f2fe6177 100644
> --- a/Documentation/ref/index.rst
> +++ b/Documentation/ref/index.rst
> @@ -42,6 +42,7 @@ time:
>     ovs-actions.7
>     ovs-appctl.8
>     ovs-ctl.8
> +   ovs-flowviz.8
>     ovs-l3ping.8
>     ovs-pki.8
>     ovs-sim.1
> diff --git a/Documentation/ref/ovs-flowviz.8.rst b/Documentation/ref/ovs-flowviz.8.rst
> new file mode 100644
> index 000000000..969fda9be
> --- /dev/null
> +++ b/Documentation/ref/ovs-flowviz.8.rst
> @@ -0,0 +1,535 @@
> +..
> +      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.
> +
> +      Convention for heading levels in Open vSwitch documentation:
> +
> +      =======  Heading 0 (reserved for the title in a document)
> +      -------  Heading 1
> +      ~~~~~~~  Heading 2
> +      +++++++  Heading 3
> +      '''''''  Heading 4
> +
> +      Avoid deeper levels because they do not render well.
> +
> +===========
> +ovs-flowviz
> +===========
> +
> +Synopsis
> +========
> +
> +``ovs-flowviz``
> +[``[-i | --input] <[alias,]file>``]
> +[``[-c | --config] <file>``]
> +[``[-f | --filter] <filter>``]
> +[``[-h | --highlight] <filter>``]
> +[``--style <style>``]
> +*<flow_type>* *<format>* [<arg>...]
> +
> +``ovs-flowviz --help``
> +
> +Description
> +===========
> +
> +The ``ovs-flowviz`` program helps visualize OpenFlow and datapath flow dumps
> +in different formats in order to make them more easily understood.
> +
> +The program works by reading flows from ``stdin`` or from a file specified
> +in the ``--input`` option, filtering them, highlighting them, and finally
> +outputting them in one of the predefined formats.
> +
> +
> +Options
> +=======
> +
> +.. program: ovs-flowviz
> +
> +.. option:: -h, --help
> +
> +    Prints a brief help message to the console.
> +
> +.. option:: -i <[alias,]file>, --input <[alias,]file>
> +
> +    Specifies the file to read flows from. If not provided, ``ovs-flowviz``
> +    will read flows from stdin.
> +
> +    This option can be specified multiple times.
> +    The file path can prepended by an alias that will be shown in the output.
> +    For example: ``--input node1,/path/to/file1 --input node2,/path/to/file2``
> +
> +.. option:: -c <file>, --config <file>
> +
> +    Specifies the style configuration file to use. ``ovs-flowviz`` ships with
> +    a default configuration file but it can be overridden using this option.
> +    Styles defined in the style configuration file will be select-able using
> +    the ``--style`` option.
> +
> +    For more details on the style configuration file, see
> +    `Style Configuration File`_ section below.
> +
> +.. option:: -f <filter>, --filter <filter>
> +
> +   Tells ``ovs-flowviz`` to filter the flows and only show the ones that match
> +   the expression (although some formats implement filtering differently,
> +   see `Datapath tree format`_ below).
> +
> +   The filtering syntax is detailed in `Filtering Syntax`_.
> +
> +.. option:: -h <filter>, --highlight <filter>
> +
> +   Tells ``ovs-flowviz`` to highlight the flows that match the provided filter
> +
> +   The filtering syntax is detailed in `Filtering Syntax`_.
> +
> +.. option:: --style <style>
> +
> +   Specifies the style to use. The style must have been defined in the
> +   style configuration file.
> +
> +.. option:: <flow_type>
> +
> +   "openflow" or "datapath".
> +
> +.. option:: <format>
> +
> +   See `Supported formats`_ section.
> +
> +
> +Supported formats
> +=================
> +
> +``ovs-flowviz`` supports several visualization formats for both OpenFlow and
> +datapath flows that are summarized in the following table:
> +
> +.. list-table::
> +   :widths: 20 10 70
> +   :align: center
> +   :header-rows: 1
> +
> +   * - Flow Type
> +     - Format
> +     - Description
> +   * - Both
> +     - console
> +     - Prints the flows in a configurable, colorful style in the console.
> +   * - Both
> +     - json
> +     - Prints the flows in JSON format.
> +   * - Both
> +     - html
> +     - Prints the flows in an HTML list.
> +   * - Openflow
> +     - cookie
> +     - Prints the flows in the console sorted by cookie.
> +   * - Openflow
> +     - logic
> +     - Prints the logical structure of flows in the console.
> +   * - Datapath
> +     - tree
> +     - Prints the flows a tree structure arranged by `recirc_id`.

Prints the flows as a tree structure arranged by `recirc_id`

> +   * - Datapath
> +     - graph
> +     - Prints a graphviz graph of the flows arranged by `recirc_id`.
> +
> +
> +Console format
> +~~~~~~~~~~~~~~
> +
> +The ``console`` works for both OpenFlow and datapath flow types and prints
> +flows in the terminal with the style determined by the ``--style`` option.
> +
> +Additionally, it accepts the following arguments:
> +
> +.. option:: -h, --heat-map
> +
> +   This option changes the color of the packet and byte counters to reflect
> +   their relative size. The color gradient goes through the following colors:
> +   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
> +
> +   Note filtering is applied before the range is calculated.
> +
> +
> +JSON format
> +~~~~~~~~~~~
> +
> +The ``json`` format works for both OpenFlow and datapath flow types and prints
> +flows in JSON format. See `JSON Syntax`_ for more details.
> +
> +
> +HTML format
> +~~~~~~~~~~~
> +
> +The ``html`` format works for both OpenFlow and datapath flows and prints
> +flows in an HTML table that offers some basic interactivity. OpenFlow flows
> +are sorted in tables and datapath flows are arranged in flow trees
> +(see `Datapath tree format`_ for more details).
> +
> +Styles defined via Style Configuration File and selected via ``--style`` option
> +also apply to ``html`` format.
> +
> +
> +OpenFlow cookie format
> +~~~~~~~~~~~~~~~~~~~~~~
> +
> +The OpenFlow ``cookie`` format is similar to the ``console`` format but
> +instead of arranging the flows per table, it arranges the flows per cookie.
> +
> +
> +Openflow logic format
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +The OpenFlow ``logic`` format helps visualize the logic structure of OpenFlow
> +pipelines by arranging flows into *logical blocks*.
> +A logical block is a set of flows that have:
> +
> +* Same ``priority``.
> +* Match on the same fields (regardless of the match value and mask).
> +* Execute the same actions (regardless of the actions' arguments,
> +  except for resubmit and output).
> +* Optionally, the ``cookie`` can be counted as part of the logical flow.
> +
> +This format supports the following extra arguments:
> +
> +.. option:: -s, --show-flows
> +
> +    Show all the flows under each logical block.
> +
> +.. option:: -d, --ovn-detrace
> +
> +    Use ovn-detrace.py script to extract cookie information (implies '-c').
> +
> +.. option:: -c, --cookie
> +
> +    Consider the cookie in the logical block.
> +
> +.. option:: --ovn-detrace-path <path>
> +
> +    Use an alternative path to look for ovn_detrace.py script.
> +
> +.. option:: --ovnnb-db text
> +
> +   Specify the OVN NB database string (implies '-d').
> +   Default value is "unix:/var/run/ovn/ovnnb_db.sock".
> +
> +.. option:: --ovnsb-db text
> +
> +   Specify the OVN SB database string (implies '-d').
> +   Default value is "unix:/var/run/ovn/ovnsb_db.sock".
> +
> +.. option:: --o <text>, --ovn-filter <text>
> +
> +   Specify the a filter to be run on the ovn-detrace information.

Specify the a filter -> Specify the filter

> +   Syntax: python regular expression
> +   (See https://docs.python.org/3/library/re.html).
> +
> +.. option:: -h, --heat-map
> +
> +   This option changes the color of the packet and byte counters to reflect
> +   their relative size. The color gradient goes through the following colors:
> +   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
> +
> +   Note filtering is applied before the range is calculated.
> +
> +
> +Datapath tree format
> +~~~~~~~~~~~~~~~~~~~~
> +
> +The datapath ``tree`` format arranges datapath flows in a hierarchical tree.
> +The tree is comprised of blocks with the same `recirc_id` and `in_port`.
> +Within those blocks, flows with the same action are combined and matches

are combined, and matches

> +which are the same are omitted to try to reduce the visual noise.
> +
> +When an flow's actions includes the `recirc()` action with a specific
> +`recirc_id`, flows matching on that `recirc_id` and `in_port` are listed
> +below. This is done recursively for all actions.
> +
> +The result is a hierarchical representation that helps understand how actions
> +are related to each other via recirculation. Note that flows with a specific
> +non-zero `recirc_id` are listed below each group of flows that have a
> +corresponding `recirc()` action.
> +Therefore, the output contains duplicated flows and can be lengthy.
> +
> +Also, filtering works in a slightly different way for datapath flow trees.
> +Unlike other formats where a filter simply removes non-matching flows,
> +the output of a filtered datapath flow tree will show full sub-trees
> +that contain at least one flow that satisfies the filter.
> +
> +The ``html`` format prints this same tree in an interactive HTML table and
> +the ``graph`` format shows the same tree in a graphviz graph.
> +
> +
> +Datapath graph format
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +The datapath ``graph`` generates a graphviz visual representation of the
> +same tree-like flow hierarchy that the ``tree`` format prints.
> +
> +It supports the following extra argument:
> +
> +.. option:: -h, --html
> +
> +    Prints the graphviz format in an svg image alongside the interactive HTML
> +    table of flows (that 'html' format would print).
> +
> +
> +JSON Syntax
> +===========
> +
> +Both OpenFlow and datapath `json` formats print a JSON list of JSON
> +objects each of one representing an individual flow.

objects each of one, remove ‘of’.

> +
> +Each flow object contains the following keys:
> +
> +**orig**
> +    Contains the original flow string.
> +
> +
> +**info**
> +   Contains an object with the flow information
> +   such as: cookie, duration, table, n_packets, n_bytes, etc.
> +
> +
> +**match**
> +   Contains an object with the flow match.
> +   For each match, the object contains a key-value where the key is the name
> +   of the match as defined in ovs-fields and ovs-ofctl and the value
> +   represents the match value. The way each value is represented depends on its
> +   type. See `Value representation`_.
> +
> +
> +**actions**
> +   Contains a list of action objects.
> +   Each action is represented by an JSON object that has one key and one value.
> +   The key corresponds to the action name. The value represents the arguments
> +   of such key. See `Action representation`_.
> +
> +
> +**ufid**
> +   (datapath flows only) Contains the ufid.
> +
> +
> +Value representation
> +~~~~~~~~~~~~~~~~~~~~
> +
> +Values are represented differently depending on their type:
> +
> +* Flags: Fields that represent flags (e.g: tcp) are represented by boolean
> +  "true"
> +
> +* Decimal / Hexadecimal: They are represented by their integer value.
> +  If they support masking, they are represented by a dictionary with two keys:
> +  value contains the field value and mask contains the mask. Both are integers.
> +
> +* Ethernet: They are represented by a string: {address}[/{mask}]
> +
> +* IPv4 / IPv6: They are represented by a string {address}[/mask]
> +
> +* Registers: They are represented by a dictionary with three keys:
> +  field contains the field value (string), start and end that represent the

“(string), start and end that” -> “(string), start, and end that”

> +  first and last bit of the register.
> +
> +For example, the register
> +::
> +
> +
> +   NXM_NX_REG10[0..15]
> +
> +
> +is represented as
> +::
> +
> +
> +   {
> +       "field": "NXM_NX_REG10",
> +       "start": 0,
> +       "end": 15
> +   },
> +
> +
> +Action representation
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +Actions are generally represented by an object that has a single key and a
> +value. The key is the action name as defined ovs-actions.
> +
> +The value of actions that have no arguments (such as ``drop``) is
> +(boolean) ``true``.
> +
> +The value of actions that have a list of arguments (e.g:
> +``resubmit([port],[table],[ct])``) is an object that has the name of the
> +argument as key. The argument names for each action is defined in
> +ovs-actions. For example, the action
> +::
> +
> +   resubmit(,10)
> +
> +is represented as
> +::
> +
> +   {
> +       "redirect": {
> +           "port": "",
> +           "table": 10
> +       }
> +   }
> +
> +The value of actions that have a key-word list as arguments
> +(e.g: ``ct([argument])``) is an object whose keys correspond to the keys
> +defined in ``ovs-actions(7)``. The way values are represented depends
> +on the type of the argument.
> +For example, the action
> +::
> +
> +   ct(table=14,zone=NXM_NX_REG12[0..15],nat)
> +
> +is represented as
> +::
> +
> +   {
> +       "ct": {
> +           "table": 14,
> +           "zone": {
> +               "field": "NXM_NX_REG12",
> +               "start": 0,
> +               "end": 15
> +           },
> +           "nat": true
> +       }
> +   }
> +
> +
> +Style Configuration File
> +========================
> +
> +The style configuration file that can be selected via the ``--config`` option
> +has INI syntax and can define any number of styles to be used by both
> +``console`` and ``html`` formats. Once defined in the configuration file
> +they can be selected using the ``--style`` option.
> +
> +INI sections are used to define styles, ``[styles.mystyle]`` defines a style
> +called `mystle`. Within a section styles can be defined as:
> +
> +::
> +
> +     [FORMAT].[PORTION].[SELECTOR].[ELEMENT] = [VALUE]
> +
> +
> +**FORMAT**
> +   Either ``console`` or ``html``
> +
> +**PORTION**
> +   The part of the a key-value the style applies to. It can be:

The part of the a key-value the style applies to. ->
The part of the key-value the style applies to.

> +   ``key`` (to indicate the key part of a key-value), ``value`` (to indicate
> +   the value part of a key-value), ``flag`` (to indicate a single flag)
> +   or ``delim`` (to indicate delimiters such as parentheses, brackets, etc).
> +
> +**SELECTOR**
> +   Is used to select what key-value the style applies to. It can be:
> +   ``highlighted`` (to indicate highlighted key-values), ``type.<type>``
> +   to indicate certain types such as `IPAddress` or `EthMask` or `<keyname>`
> +   to select a particular key name.
> +
> +**ELEMENT**
> +   Is used to select what style element to modify. It can be one
> +   of: **color** or **underline** (only for **console** format).
> +
> +**VALUE**
> +   Is either a color hex, other color names defined in the rich python

Is either a color hex, other color names defined in the rich python ->
Is either a color hex, or color name defined in the rich python

> +   library (https://rich.readthedocs.io/en/stable/appendix/colors.html) or
> +   "true" if the element is ``underline``.
> +
> +A default configuration file is shipped with the tool and its path is printed
> +in the ``--help`` output. A detailed description of the syntax alongside
> +some examples are available there.
> +
> +
> +Filtering syntax
> +================
> +
> +``ovs-flowviz`` provides rich highlighting and filtering. The special command
> +``ovs-flowviz filter`` dumps the filtering syntax:
> +
> +::
> +
> +    $ ovs-flowviz filter
> +    Filter Syntax
> +    *************
> +
> +       [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ...
> +
> +      Comparison operators are:
> +          =   equality
> +          <   less than
> +          >   more than
> +          ~=  masking (valid for IP and Ethernet fields)
> +
> +      Logical operators are:
> +          !{expr}:  NOT
> +          {expr} && {expr}: AND
> +          {expr} || {expr}: OR
> +
> +      Matches and flow metadata:
> +          To compare against a match or info field, use the field directly, e.g:
> +              priority=100
> +              n_bytes>10
> +          Use simple keywords for flags:
> +              tcp and ip_src=192.168.1.1
> +
> +      Actions:
> +          Actions values might be dictionaries, use subkeys to access individual
> +          values, e.g:
> +              output.port=3
> +          Use simple keywords for flags
> +              drop
> +
> +      Examples of valid filters.
> +          nw_addr~=192.168.1.1 && (tcp_dst=80 || tcp_dst=443)
> +          arp=true && !arp_tsa=192.168.1.1
> +          n_bytes>0 && drop=true
> +
> +
> +Example expressions:
> +::
> +
> +   n_bytes > 0 and drop
> +   nw_src~=192.168.1.1 or arp.tsa=192.168.1.1
> +   ! tcp && output.port=2
> +
> +
> +Examples
> +========
> +
> +Print OpenFlow flows sorted by cookie adding OVN data to each one:
> +::
> +
> +    $ ovs-flowviz -i flows.txt openflow cookie --ovn-detrace
> +
> +Print OpenFlow logical structure, showing the flows and heat-map:
> +::
> +
> +    $ ovs-flowviz -i flows.txt openflow logic --show-flows --heat-map
> +
> +Display OpenFlow flows in HTML format with "light" style and highlight drops:
> +::
> +
> +    $ ovs-flowviz -i flows.txt --style "light" --highlight "n_packets > 0 and drop" openflow html > flows.html
> +
> +Display the datapath flows in an interactive graphviz + HTML view:
> +::
> +
> +    $ ovs-flowviz -i flows.txt datapath graph --html > flows.html
> +
> +Display the datapath flow trees that lead to packets being sent to port 10:
> +::
> +
> +    $ ovs-flowviz -i flows.txt --filter "output.port=10" datapath tree
> diff --git a/Documentation/topics/flow-visualization.rst b/Documentation/topics/flow-visualization.rst
> new file mode 100644
> index 000000000..fe4aeacaf
> --- /dev/null
> +++ b/Documentation/topics/flow-visualization.rst
> @@ -0,0 +1,314 @@
> +..
> +      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.
> +
> +      Convention for heading levels in Open vSwitch documentation:
> +
> +      =======  Heading 0 (reserved for the title in a document)
> +      -------  Heading 1
> +      ~~~~~~~  Heading 2
> +      +++++++  Heading 3
> +      '''''''  Heading 4
> +
> +      Avoid deeper levels because they do not render well.
> +
> +==================================
> +Visualizing flows with ovs-flowviz
> +==================================
> +
> +When troubleshooting networking issues with OVS, we typically end up looking
> +at OpenFlow or datapath flow dumps. These dumps tend to be quite dense and
> +difficult to reason about.
> +
> +``ovs-flowviz`` is a utility script that helps visualizing OpenFlow and

visualizing -> to visualize

> +datapath flows to make it easier to understand what is going on.
> +
> +The `ovs-flowviz(8)`_ manpage describes its basic usage. In this document a few
> +of its advanced visualization formats will be expanded.
> +
> +
> +Installing ovs-flowviz
> +----------------------
> +
> +``ovs-flowviz`` is part of the openvswitch python package but its
> +extra dependencies have to be installed explicitly by running:
> +::
> +
> +    $ pip install openvswitch[flowviz]
> +
> +Or, if you are working with the OVS tree:
> +::
> +
> +    $ cd python && pip install .[flowviz]
> +
> +Visualizing OpenFlow logical block
> +----------------------------------
> +
> +When controllers such as OVN write OpenFlow flows, they typically organize
> +flows in functional blocks. These blocks can expand to multiple flows that
> +"look similar", in the sense that they match on the same fields and have
> +similar actions.
> +
> +However, when we look at a flow dump the number of flows can make it difficult
> +to perceive this logical functionality that the controller is trying to
> +implement using OpenFlow.
> +
> +In this example, we are going to use ``ovs-flowviz openflow logic``
> +visualization to understand an OVN flow dump a bit better.
> +
> +On a particular flow dump we have 23 flows in table 0:
> +::
> +
> +   $ grep -c "table=0" flows.txt
> +   23
> +
> +If we look at the first few lines, the amount of information can be
> +overwhelming and difficult our analysis:
> +
> +::
> +
> +    $ head flows.txt
> +      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,vlan_tci=0x0000/0x1000 actions=conjunction(100,2/2)
> +      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,conj_id=100,in_port="patch-br-int-to",vlan_tci=0x0000/0x1000 actions=load:0xa->NXM_NX_REG13[],load:0xc->NXM_NX_REG11[],load:0xb->NXM_NX_REG12[],load:0xb->OXM_OF_METADATA[],load:0x1->NXM_NX_REG14[],mod_dl_src:02:42:ac:12:00:03,resubmit(,8)
> +      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-6bb3b3-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
> +      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-a6ff98-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
> +      cookie=0xf2ca6195, duration=765.107s, table=0, n_packets=6, n_bytes=636, priority=100,in_port="ovn-k8s-mp0" actions=load:0x1->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0x236e941d, duration=408.874s, table=0, n_packets=11, n_bytes=846, priority=100,in_port=aceac9829941d11 actions=load:0x11->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x3->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0x3facf689, duration=405.581s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="363ba22029cd92b" actions=load:0x12->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x4->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0xe7c8c4bb, duration=405.570s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="6a62cde0d50ef44" actions=load:0x13->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x5->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0x99a0ffc1, duration=59.391s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="5ff3bfaaa4eb622" actions=load:0x14->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x6->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0xe1b5c263, duration=59.365s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="8d9e0bc76347e59" actions=load:0x15->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x7->NXM_NX_REG14[],resubmit(,8)
> +
> +
> +However, we can better understand what table 0 does by looking at its
> +logical representation.
> +::
> +
> +   $ ovs-flowviz -i flows.txt -f "table=0" openflow logic
> +    Ofproto Flows (logical)
> +    └── ** TABLE 0 **
> +        ├── priority=180 priority,vlan_tci  --->  conjunction ( x 1 )
> +        ├── priority=180 priority,conj_id,in_port,vlan_tci  --->  load,load,load,load,load,mod_dl_src resubmit(,8), ( x 1 )
> +        ├── priority=100 priority,in_port  --->  move,move,move resubmit(,40), ( x 2 )
> +        ├── priority=100 priority,in_port  --->  load,load,load,load,load resubmit(,8), ( x 16 )
> +        ├── priority=100 priority,in_port,vlan_tci  --->  load,load,load,load,load resubmit(,8), ( x 1 )
> +        ├── priority=100 priority,in_port,dl_vlan  --->  strip_vlan,load,load,load,load,load resubmit(,8), ( x 1 )
> +        └── priority=0 priority  --->   drop, ( x 1 )
> +
> +
> +In only a few logical blocks, we have a good overview of what this table is
> +doing. It looks like it's adding metadata based on input ports and vlan
> +IDs and mainly sending traffic to table 8.
> +
> +Let's look at table 8, an in this case, let's filter out the flows that have

Let's look at table 8, an in this case, ->
Let's look at table 8, and in this case,

> +not been hit by actual traffic. This is quite easy to do with the arithmetic
> +filtering expressions:
> +::
> +
> +   $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic
> +
> +    Ofproto Flows (logical)
> +    └── ** TABLE 8 **
> +        ├── priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 3 )
> +        └── priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
> +
> +At this point, we might find ourselves a bit lost since we may not remember
> +what metadata OVN stored in the previous table. Here is where
> +``ovs-flowviz``'s OVN integration could come useful. Let's connect to the

OVN integration could come useful. ->
OVN integration could be useful.

> +running OVN instance and ask it about the flows we're looking at.
> +
> +::
> +
> +    $ export OVN_NB_DB=tcp:172.18.0.4:6641
> +    $ export OVN_SB_DB=tcp:172.18.0.4:6642
> +    $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic --ovn-detrace
> +    Ofproto Flows (logical)
> +    └── ** TABLE 8 **
> +        ├── cookie=0xe10c34ee priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> +        │   └── OVN Info
> +        │       ├── *  Logical datapaths:
> +        │       ├── *      "ovn_cluster_router" (366e1c41-0f3d-4420-b796-10692b64e3e4)
> +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtos-ovn-worker2), actions=(xreg0[0..47] = 0a:58:0a:f4:01:01; next;)
> +        │       └── *  Logical Router Port: rtos-ovn-worker2 mac 0a:58:0a:f4:01:01 networks ['10.244.1.1/24'] ipv6_ra_configs {}
> +        ├── cookie=0x11e1adbc priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> +        │   └── OVN Info
> +        │       ├── *  Logical datapaths:
> +        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
> +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
> +        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
> +        ├── cookie=0xf42133f  priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> +        │   └── OVN Info
> +        │       ├── *  Logical datapaths:
> +        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
> +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.dst == 02:42:ac:12:00:03 && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
> +        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
> +        └── cookie=0x43a0327  priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
> +            └── OVN Info
> +                ├── *  Logical datapaths:
> +                ├── *      "ovn-worker" (24280d0b-fee0-4f8e-ba4f-036a9b9af921)
> +                ├── *      "ovn-control-plane" (3262a782-8961-416b-805e-08233e8fda72)
> +                ├── *      "ext_ovn-worker2" (3f88dcd2-c56d-478f-a3b1-c7aee2efe967)
> +                ├── *      "ext_ovn-worker" (5facbaf0-485d-4cf5-8940-eff9678ef7bb)
> +                ├── *      "ext_ovn-control-plane" (8b0aecb6-b05a-48a7-ad09-72524bb91d40)
> +                ├── *      "join" (e2dc230e-2f2a-4b93-93fa-0fe495163514)
> +                ├── *      "ovn-worker2" (f7709fbf-d728-4cff-9b9b-150461cc75d2)
> +                └── *  Logical flow: table=0 (ls_in_check_port_sec), priority=50, match=(1), actions=(reg0[15] = check_in_port_sec(); next;)
> +
> +That's way better. ``ovs-flowviz`` has automatically added the `cookie` to the
> +logical block key so have more blocks but in exchange, it has looked up each
> +cookie on the running OVN databases and inserted the known information on each

inserted -> insert

> +block. So now we see what OVN is trying to do, the logical flow that generated
> +each OpenFlow flow and the logical datapath each flow belongs to.
> +
> +Visualizing datapath flow trees
> +-------------------------------
> +
> +Now, let's see another typical usecase that can lead to eyestrain:
> +understanding datapath conntrack recirculations.
> +
> +OVS makes heavy use of connection tracking and the ``recirc()`` action
> +to build complex datapaths. Typically, OVS will insert a flow that,
> +when matched, will send the packet through conntrack (using the ``ct`` action)
> +and recirculate it with a particular recirculation id (``recirc_id``). Then, a
> +flow matching on that ``recirc_id`` will be matched and further process the
> +packet. This can happen more than once for a given packet.
> +
> +This sequential set of events is, however, difficult to visualize when you
> +look at a datapath flow dump. Flows are unordered so recirculations need to
> +be followed manually (typically, with heavy use of "grep").
> +
> +For this use-case, ``ovs-flowviz datapath tree`` format can be extremely
> +useful. It builds a hierarchical tree based on the ``recirc_id`` ``in_port``
> +and ``recirc()`` actions.
> +
> +Furthermore, it is common to end up with multiple flows that have the same
> +list of actions. An example of this is a number flows that perform mac/vlan
> +checks for a given port and send the traffic though the same conntrack zone.
> +In order to better visualize this and reduce the amount of duplicated flows
> +that are ineviably printed in this view, these flows are combined into a block

Not sure what you wanted to type here, so I guess; ineviably -> inevitably

> +and the match keys that are equal for all flows are removed.
> +
> +Here is an example.
> +::
> +
> +  Datapath Flows (logical)
> +  └── ╭────────────────────────────────╮
> +      │ [recirc_id(0x0) in_port(eth0)] │
> +      ╰────────────────────────────────╯
> +      └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +          │ recirc_id(0),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0/0),ct_zone(0/0),ct_mark(0/0),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                                                   │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:01),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.0.0.0/255.255.128.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=32768/0x8000,dst=0/0), packets:711, bytes:114236,                                                                                                         │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660,                                                                                                          │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:22),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:1, bytes:66,                                                                                                          │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:09),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:0, bytes:0,                                                                                                                 │
> +          ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +          └── ╭───────────────────────────────────────╮
> +              │ actions: ct(zone=32,nat),recirc(0xc1) │
> +              ╰───────────────────────────────────────╯
> +              └── ╭─────────────────────────────────╮
> +                  │ [recirc_id(0xc1) in_port(eth0)] │
> +                  ╰─────────────────────────────────╯
> +                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ip │
> +                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                  │
> +                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +                  │   └── ╭───────────────────────────────────────╮
> +                  │       │ actions: ct(zone=14,nat),recirc(0xc2) │
> +                  │       ╰───────────────────────────────────────╯
> +                  │       └── ╭─────────────────────────────────╮
> +                  │           │ [recirc_id(0xc2) in_port(eth0)] │
> +                  │           ╰─────────────────────────────────╯
> +                  │           └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +                  │               │ recirc_id(0xc2),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0x1),ct_label(0/0),eth(src=00:00:00:00:00:00/00:00:00:00:00:00,dst=00:00:00 │
> +                  │               │ :00:00:00/01:00:00:00:00:00),eth_type(0x0800),ipv4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=0/0,tos=0/0,ttl=0/0,frag=no), packets:4924, bytes:468961,                                        │
> +                  │               ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +                  │               └── ╭──────────────────────╮
> +                  │                   │ actions: ovn-k8s-mp0 │
> +                  │                   ╰──────────────────────╯
> +                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(0x0800),ip │
> +                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660                                                                          │
> +                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +
> +
> +The above shows a part of a bigger tree where a first block of flows
> +at ``recirc_id(0)`` that match on different destination ethernet
> +addresses and protocols and send traffic through conntrack (zone 32).

send -> sends
> +
> +Then some additional flows at ``recirc_id(0xc1)`` process each traffic
> +connection independently. One of them, shown in the example, sends the traffic
> +through conntrack zone 14 and after another recirculation the packet is
> +ultimately sent through a port.
> +
> +This is a truly complex multi-zone conntrack pipeline that is now fairly
> +clear thanks to this visualization.
> +
> +Also note, the flows in the block are conveniently sorted by sent packets.
> +
> +This example shows only a single "subtree". Even though the combination of
> +flows with the same action helps, if we use this command to display a big
> +dump, the output can be lengthy. Here are two (combinable) ways to
> +help out.
> +
> +
> +Plotting datapath trees
> +~~~~~~~~~~~~~~~~~~~~~~~
> +
> +By using the ``ovs-flowviz datapath html`` format, long datapath trees can
> +be displayed in an interactive HTML table. The resulting web allows you to
> +collapse and expand subtrees so you can focus on what you're looking for.
> +
> +In addition, the ``ovs-flowviz datapath graph`` format generates a graphviz
> +graph definition where each block of flows with the same ``recirc_id`` match
> +are arranged together and edges are created to represent recirculations.
> +Also, this format comes with further goodies such as displaying the conntrack
> +zones which are key to understanding what the datapath is really doing with a
> +packet.
> +
> +These two formats (``html`` and ``graph``) can even be combined. By using the
> +``ovs-flowviz datapath graph --html`` command, you'll get an interactive
> +HTML table alongside a `svg` graphical representation of the flows. Click on
> +a flow on the svg and it'll take you to the corresponding entry in the
> +flow table.
> +
> +
> +Filtering
> +~~~~~~~~~
> +
> +Apart from being able to expand and collapse subtrees, we can use filtering.
> +
> +However, filtering works in a slightly different way compared with OpenFlow
> +flows. Instead of just removing non-matching flows, the output
> +of a filtered datapath flow tree will show full sub-trees that contain at
> +least one flow that satisfies the filter.
> +
> +For example, let's take the flows in the above example, and let's imagine we
> +want to understand what traffic is going out on port ``ovn-k8s-mp0``. We
> +could run the tool as:
> +::
> +
> +   $ ovs-appctl dpctl/dump-flows | ovs-flowviz -f "output.port=ovn-k8s-mp0" datapath tree
> +
> +The resulting flow tree will contain all of the flows above, even those
> +with ``recirc_id(0)`` and ``recirc_id(0xc1)`` that don't actually output
> +traffic to port ``ovn-k8s-mp0``. Why? because they are all part of a subtree
> +that contains flows that do output packets on port ``ovn-k8s-mp0``
> +
> +That way, we see the "full picture" of how traffic on ending up in a particular

on ending -> ends

> +port is being processed.
> +
> +.. _ovs-flowviz(8): https://docs.openvswitch.org/en/latest/ref/ovs-flowviz.8
> diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst
> index f239fcf83..9ddb145dd 100644
> --- a/Documentation/topics/index.rst
> +++ b/Documentation/topics/index.rst
> @@ -58,3 +58,4 @@ OVS
>     userspace-checksum-offloading
>     userspace-tx-steering
>     usdt-probes
> +   flow-visualization
> diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
> index 94b6d7431..d228ef1d3 100644
> --- a/rhel/openvswitch-fedora.spec.in
> +++ b/rhel/openvswitch-fedora.spec.in
> @@ -499,6 +499,7 @@ fi
>  %{_mandir}/man8/ovs-ctl.8*
>  %{_mandir}/man8/ovs-dpctl.8*
>  %{_mandir}/man8/ovs-dpctl-top.8*
> +%{_mandir}/man8/ovs-flowviz.8*
>  %{_mandir}/man8/ovs-kmod-ctl.8*
>  %{_mandir}/man8/ovs-ofctl.8*
>  %{_mandir}/man8/ovs-pki.8*
> diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
> index 9903dd10a..437e01ee1 100644
> --- a/rhel/openvswitch.spec.in
> +++ b/rhel/openvswitch.spec.in
> @@ -223,6 +223,7 @@ exit 0
>  /usr/share/man/man8/ovs-ctl.8.gz
>  /usr/share/man/man8/ovs-dpctl.8.gz
>  /usr/share/man/man8/ovs-dpctl-top.8.gz
> +/usr/share/man/man8/ovs-flowviz.8.gz
>  /usr/share/man/man8/ovs-kmod-ctl.8.gz
>  /usr/share/man/man8/ovs-ofctl.8.gz
>  /usr/share/man/man8/ovs-parse-backtrace.8.gz
> -- 
> 2.45.2
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Ilya Maximets Aug. 19, 2024, 10:15 p.m. UTC | #2
On 7/10/24 19:05, Adrian Moreno wrote:
> Add a man page for ovs-flowviz as well as a topic page with some more
> detailed examples.
> 
> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
> ---
>  Documentation/automake.mk                   |   4 +-
>  Documentation/conf.py                       |   2 +
>  Documentation/ref/index.rst                 |   1 +
>  Documentation/ref/ovs-flowviz.8.rst         | 535 ++++++++++++++++++++
>  Documentation/topics/flow-visualization.rst | 314 ++++++++++++
>  Documentation/topics/index.rst              |   1 +
>  rhel/openvswitch-fedora.spec.in             |   1 +
>  rhel/openvswitch.spec.in                    |   1 +
>  8 files changed, 858 insertions(+), 1 deletion(-)
>  create mode 100644 Documentation/ref/ovs-flowviz.8.rst
>  create mode 100644 Documentation/topics/flow-visualization.rst

Hi, Adrian.  Thanks for the update!  I didn't make a full review, since
Eelco did, but I have a couple comments for the docs and the output
format below.

Best regards, Ilya Maximets.

> 
> diff --git a/Documentation/automake.mk b/Documentation/automake.mk
> index 47d2e336a..539870aa2 100644
> --- a/Documentation/automake.mk
> +++ b/Documentation/automake.mk
> @@ -45,7 +45,7 @@ DOC_SOURCE = \
>  	Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
>  	Documentation/topics/fuzzing/ovs-fuzzers.rst \
>  	Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
> -	Documentation/topics/testing.rst \
> +	Documentation/topics/flow-visualization.rst \
>  	Documentation/topics/integration.rst \
>  	Documentation/topics/language-bindings.rst \
>  	Documentation/topics/networking-namespaces.rst \
> @@ -55,6 +55,7 @@ DOC_SOURCE = \
>  	Documentation/topics/ovsdb-replication.rst \
>  	Documentation/topics/porting.rst \
>  	Documentation/topics/record-replay.rst \
> +	Documentation/topics/testing.rst \
>  	Documentation/topics/tracing.rst \
>  	Documentation/topics/usdt-probes.rst \
>  	Documentation/topics/userspace-checksum-offloading.rst \
> @@ -162,6 +163,7 @@ RST_MANPAGES = \
>  	ovs-actions.7.rst \
>  	ovs-appctl.8.rst \
>  	ovs-ctl.8.rst \
> +	ovs-flowviz.8.rst \
>  	ovs-l3ping.8.rst \
>  	ovs-parse-backtrace.8.rst \
>  	ovs-pki.8.rst \
> diff --git a/Documentation/conf.py b/Documentation/conf.py
> index 15785605a..3a82f23a7 100644
> --- a/Documentation/conf.py
> +++ b/Documentation/conf.py
> @@ -120,6 +120,8 @@ _man_pages = [
>       u'utility for configuring running Open vSwitch daemons'),
>      ('ovs-ctl.8',
>       u'OVS startup helper script'),
> +    ('ovs-flowviz.8',
> +     u'utility for visualizing OpenFlow and datapath flows'),
>      ('ovs-l3ping.8',
>       u'check network deployment for L3 tunneling problems'),
>      ('ovs-parse-backtrace.8',
> diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
> index 03ada932f..7f2fe6177 100644
> --- a/Documentation/ref/index.rst
> +++ b/Documentation/ref/index.rst
> @@ -42,6 +42,7 @@ time:
>     ovs-actions.7
>     ovs-appctl.8
>     ovs-ctl.8
> +   ovs-flowviz.8
>     ovs-l3ping.8
>     ovs-pki.8
>     ovs-sim.1
> diff --git a/Documentation/ref/ovs-flowviz.8.rst b/Documentation/ref/ovs-flowviz.8.rst
> new file mode 100644
> index 000000000..969fda9be
> --- /dev/null
> +++ b/Documentation/ref/ovs-flowviz.8.rst
> @@ -0,0 +1,535 @@
> +..
> +      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.
> +
> +      Convention for heading levels in Open vSwitch documentation:
> +
> +      =======  Heading 0 (reserved for the title in a document)
> +      -------  Heading 1
> +      ~~~~~~~  Heading 2
> +      +++++++  Heading 3
> +      '''''''  Heading 4
> +
> +      Avoid deeper levels because they do not render well.
> +
> +===========
> +ovs-flowviz
> +===========
> +
> +Synopsis
> +========
> +
> +``ovs-flowviz``
> +[``[-i | --input] <[alias,]file>``]
> +[``[-c | --config] <file>``]
> +[``[-f | --filter] <filter>``]
> +[``[-h | --highlight] <filter>``]
> +[``--style <style>``]
> +*<flow_type>* *<format>* [<arg>...]


We should be consistent with the way arguments are formatted.
This one is not rendered well.  See the following patch as an
example of how to fix the argument highlighting here and in
other places in the doc:
  https://patchwork.ozlabs.org/project/openvswitch/patch/20240819194301.1828180-1-i.maximets@ovn.org/

> +
> +``ovs-flowviz --help``
> +
> +Description
> +===========
> +
> +The ``ovs-flowviz`` program helps visualize OpenFlow and datapath flow dumps
> +in different formats in order to make them more easily understood.
> +
> +The program works by reading flows from ``stdin`` or from a file specified
> +in the ``--input`` option, filtering them, highlighting them, and finally
> +outputting them in one of the predefined formats.
> +
> +
> +Options
> +=======
> +
> +.. program: ovs-flowviz
> +
> +.. option:: -h, --help
> +
> +    Prints a brief help message to the console.
> +
> +.. option:: -i <[alias,]file>, --input <[alias,]file>
> +
> +    Specifies the file to read flows from. If not provided, ``ovs-flowviz``
> +    will read flows from stdin.
> +
> +    This option can be specified multiple times.
> +    The file path can prepended by an alias that will be shown in the output.
> +    For example: ``--input node1,/path/to/file1 --input node2,/path/to/file2``
> +
> +.. option:: -c <file>, --config <file>
> +
> +    Specifies the style configuration file to use. ``ovs-flowviz`` ships with
> +    a default configuration file but it can be overridden using this option.
> +    Styles defined in the style configuration file will be select-able using
> +    the ``--style`` option.
> +
> +    For more details on the style configuration file, see
> +    `Style Configuration File`_ section below.
> +
> +.. option:: -f <filter>, --filter <filter>
> +
> +   Tells ``ovs-flowviz`` to filter the flows and only show the ones that match
> +   the expression (although some formats implement filtering differently,
> +   see `Datapath tree format`_ below).
> +
> +   The filtering syntax is detailed in `Filtering Syntax`_.
> +
> +.. option:: -h <filter>, --highlight <filter>
> +
> +   Tells ``ovs-flowviz`` to highlight the flows that match the provided filter
> +
> +   The filtering syntax is detailed in `Filtering Syntax`_.
> +
> +.. option:: --style <style>
> +
> +   Specifies the style to use. The style must have been defined in the
> +   style configuration file.
> +
> +.. option:: <flow_type>
> +
> +   "openflow" or "datapath".
> +
> +.. option:: <format>
> +
> +   See `Supported formats`_ section.
> +
> +
> +Supported formats
> +=================
> +
> +``ovs-flowviz`` supports several visualization formats for both OpenFlow and
> +datapath flows that are summarized in the following table:
> +
> +.. list-table::
> +   :widths: 20 10 70
> +   :align: center
> +   :header-rows: 1
> +
> +   * - Flow Type
> +     - Format
> +     - Description
> +   * - Both
> +     - console
> +     - Prints the flows in a configurable, colorful style in the console.
> +   * - Both
> +     - json
> +     - Prints the flows in JSON format.
> +   * - Both
> +     - html
> +     - Prints the flows in an HTML list.
> +   * - Openflow
> +     - cookie
> +     - Prints the flows in the console sorted by cookie.
> +   * - Openflow
> +     - logic
> +     - Prints the logical structure of flows in the console.
> +   * - Datapath
> +     - tree
> +     - Prints the flows a tree structure arranged by `recirc_id`.
> +   * - Datapath
> +     - graph
> +     - Prints a graphviz graph of the flows arranged by `recirc_id`.
> +

For some reason this table breaks in terminal window with 88+ columns...
Not sure what is going on there.  Might be not related to the table
itself...

> +
> +Console format
> +~~~~~~~~~~~~~~
> +
> +The ``console`` works for both OpenFlow and datapath flow types and prints
> +flows in the terminal with the style determined by the ``--style`` option.
> +
> +Additionally, it accepts the following arguments:
> +
> +.. option:: -h, --heat-map
> +
> +   This option changes the color of the packet and byte counters to reflect
> +   their relative size. The color gradient goes through the following colors:
> +   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
> +
> +   Note filtering is applied before the range is calculated.
> +
> +
> +JSON format
> +~~~~~~~~~~~
> +
> +The ``json`` format works for both OpenFlow and datapath flow types and prints
> +flows in JSON format. See `JSON Syntax`_ for more details.
> +
> +
> +HTML format
> +~~~~~~~~~~~
> +
> +The ``html`` format works for both OpenFlow and datapath flows and prints
> +flows in an HTML table that offers some basic interactivity. OpenFlow flows
> +are sorted in tables and datapath flows are arranged in flow trees
> +(see `Datapath tree format`_ for more details).
> +
> +Styles defined via Style Configuration File and selected via ``--style`` option
> +also apply to ``html`` format.
> +
> +
> +OpenFlow cookie format
> +~~~~~~~~~~~~~~~~~~~~~~
> +
> +The OpenFlow ``cookie`` format is similar to the ``console`` format but
> +instead of arranging the flows per table, it arranges the flows per cookie.
> +
> +
> +Openflow logic format
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +The OpenFlow ``logic`` format helps visualize the logic structure of OpenFlow
> +pipelines by arranging flows into *logical blocks*.
> +A logical block is a set of flows that have:
> +
> +* Same ``priority``.
> +* Match on the same fields (regardless of the match value and mask).
> +* Execute the same actions (regardless of the actions' arguments,
> +  except for resubmit and output).
> +* Optionally, the ``cookie`` can be counted as part of the logical flow.
> +
> +This format supports the following extra arguments:
> +
> +.. option:: -s, --show-flows
> +
> +    Show all the flows under each logical block.
> +
> +.. option:: -d, --ovn-detrace
> +
> +    Use ovn-detrace.py script to extract cookie information (implies '-c').
> +
> +.. option:: -c, --cookie
> +
> +    Consider the cookie in the logical block.
> +
> +.. option:: --ovn-detrace-path <path>
> +
> +    Use an alternative path to look for ovn_detrace.py script.
> +
> +.. option:: --ovnnb-db text
> +
> +   Specify the OVN NB database string (implies '-d').
> +   Default value is "unix:/var/run/ovn/ovnnb_db.sock".
> +
> +.. option:: --ovnsb-db text
> +
> +   Specify the OVN SB database string (implies '-d').
> +   Default value is "unix:/var/run/ovn/ovnsb_db.sock".
> +
> +.. option:: --o <text>, --ovn-filter <text>
> +
> +   Specify the a filter to be run on the ovn-detrace information.
> +   Syntax: python regular expression
> +   (See https://docs.python.org/3/library/re.html).
> +
> +.. option:: -h, --heat-map
> +
> +   This option changes the color of the packet and byte counters to reflect
> +   their relative size. The color gradient goes through the following colors:
> +   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
> +
> +   Note filtering is applied before the range is calculated.
> +
> +
> +Datapath tree format
> +~~~~~~~~~~~~~~~~~~~~
> +
> +The datapath ``tree`` format arranges datapath flows in a hierarchical tree.
> +The tree is comprised of blocks with the same `recirc_id` and `in_port`.
> +Within those blocks, flows with the same action are combined and matches
> +which are the same are omitted to try to reduce the visual noise.
> +
> +When an flow's actions includes the `recirc()` action with a specific
> +`recirc_id`, flows matching on that `recirc_id` and `in_port` are listed
> +below. This is done recursively for all actions.
> +
> +The result is a hierarchical representation that helps understand how actions
> +are related to each other via recirculation. Note that flows with a specific
> +non-zero `recirc_id` are listed below each group of flows that have a
> +corresponding `recirc()` action.
> +Therefore, the output contains duplicated flows and can be lengthy.
> +
> +Also, filtering works in a slightly different way for datapath flow trees.
> +Unlike other formats where a filter simply removes non-matching flows,
> +the output of a filtered datapath flow tree will show full sub-trees
> +that contain at least one flow that satisfies the filter.
> +
> +The ``html`` format prints this same tree in an interactive HTML table and
> +the ``graph`` format shows the same tree in a graphviz graph.
> +
> +
> +Datapath graph format
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +The datapath ``graph`` generates a graphviz visual representation of the
> +same tree-like flow hierarchy that the ``tree`` format prints.
> +
> +It supports the following extra argument:
> +
> +.. option:: -h, --html
> +
> +    Prints the graphviz format in an svg image alongside the interactive HTML
> +    table of flows (that 'html' format would print).
> +
> +
> +JSON Syntax
> +===========
> +
> +Both OpenFlow and datapath `json` formats print a JSON list of JSON
> +objects each of one representing an individual flow.
> +
> +Each flow object contains the following keys:
> +
> +**orig**
> +    Contains the original flow string.
> +
> +
> +**info**
> +   Contains an object with the flow information
> +   such as: cookie, duration, table, n_packets, n_bytes, etc.
> +
> +
> +**match**
> +   Contains an object with the flow match.
> +   For each match, the object contains a key-value where the key is the name
> +   of the match as defined in ovs-fields and ovs-ofctl and the value
> +   represents the match value. The way each value is represented depends on its
> +   type. See `Value representation`_.
> +
> +
> +**actions**
> +   Contains a list of action objects.
> +   Each action is represented by an JSON object that has one key and one value.
> +   The key corresponds to the action name. The value represents the arguments
> +   of such key. See `Action representation`_.
> +
> +
> +**ufid**
> +   (datapath flows only) Contains the ufid.
> +
> +
> +Value representation
> +~~~~~~~~~~~~~~~~~~~~
> +
> +Values are represented differently depending on their type:
> +
> +* Flags: Fields that represent flags (e.g: tcp) are represented by boolean
> +  "true"
> +
> +* Decimal / Hexadecimal: They are represented by their integer value.
> +  If they support masking, they are represented by a dictionary with two keys:
> +  value contains the field value and mask contains the mask. Both are integers.
> +
> +* Ethernet: They are represented by a string: {address}[/{mask}]
> +
> +* IPv4 / IPv6: They are represented by a string {address}[/mask]
> +
> +* Registers: They are represented by a dictionary with three keys:
> +  field contains the field value (string), start and end that represent the
> +  first and last bit of the register.
> +
> +For example, the register
> +::
> +
> +
> +   NXM_NX_REG10[0..15]
> +
> +
> +is represented as
> +::
> +
> +
> +   {
> +       "field": "NXM_NX_REG10",
> +       "start": 0,
> +       "end": 15
> +   },
> +
> +
> +Action representation
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +Actions are generally represented by an object that has a single key and a
> +value. The key is the action name as defined ovs-actions.
> +
> +The value of actions that have no arguments (such as ``drop``) is
> +(boolean) ``true``.
> +
> +The value of actions that have a list of arguments (e.g:
> +``resubmit([port],[table],[ct])``) is an object that has the name of the
> +argument as key. The argument names for each action is defined in
> +ovs-actions. For example, the action
> +::
> +
> +   resubmit(,10)
> +
> +is represented as
> +::
> +
> +   {
> +       "redirect": {
> +           "port": "",
> +           "table": 10
> +       }
> +   }
> +
> +The value of actions that have a key-word list as arguments
> +(e.g: ``ct([argument])``) is an object whose keys correspond to the keys
> +defined in ``ovs-actions(7)``. The way values are represented depends
> +on the type of the argument.
> +For example, the action
> +::
> +
> +   ct(table=14,zone=NXM_NX_REG12[0..15],nat)
> +
> +is represented as
> +::
> +
> +   {
> +       "ct": {
> +           "table": 14,
> +           "zone": {
> +               "field": "NXM_NX_REG12",
> +               "start": 0,
> +               "end": 15
> +           },
> +           "nat": true
> +       }
> +   }
> +
> +
> +Style Configuration File
> +========================

Not related to the doc, but somehow by running:

$ python3 -m pip install .[flowviz]

inside the venv, the conf file ends up in a root directory:

$ find ./ -iname 'ovs-flowviz.conf'
./venv/ovs-flowviz.conf
./venv/lib/python3.12/site-packages/ovs/flowviz/ovs-flowviz.conf


> +
> +The style configuration file that can be selected via the ``--config`` option
> +has INI syntax and can define any number of styles to be used by both
> +``console`` and ``html`` formats. Once defined in the configuration file
> +they can be selected using the ``--style`` option.
> +
> +INI sections are used to define styles, ``[styles.mystyle]`` defines a style
> +called `mystle`. Within a section styles can be defined as:
> +
> +::
> +
> +     [FORMAT].[PORTION].[SELECTOR].[ELEMENT] = [VALUE]
> +
> +
> +**FORMAT**
> +   Either ``console`` or ``html``
> +
> +**PORTION**
> +   The part of the a key-value the style applies to. It can be:
> +   ``key`` (to indicate the key part of a key-value), ``value`` (to indicate
> +   the value part of a key-value), ``flag`` (to indicate a single flag)
> +   or ``delim`` (to indicate delimiters such as parentheses, brackets, etc).
> +
> +**SELECTOR**
> +   Is used to select what key-value the style applies to. It can be:
> +   ``highlighted`` (to indicate highlighted key-values), ``type.<type>``
> +   to indicate certain types such as `IPAddress` or `EthMask` or `<keyname>`
> +   to select a particular key name.
> +
> +**ELEMENT**
> +   Is used to select what style element to modify. It can be one
> +   of: **color** or **underline** (only for **console** format).
> +
> +**VALUE**
> +   Is either a color hex, other color names defined in the rich python
> +   library (https://rich.readthedocs.io/en/stable/appendix/colors.html) or
> +   "true" if the element is ``underline``.
> +
> +A default configuration file is shipped with the tool and its path is printed
> +in the ``--help`` output. A detailed description of the syntax alongside
> +some examples are available there.
> +
> +
> +Filtering syntax
> +================
> +
> +``ovs-flowviz`` provides rich highlighting and filtering. The special command
> +``ovs-flowviz filter`` dumps the filtering syntax:
> +
> +::
> +
> +    $ ovs-flowviz filter
> +    Filter Syntax
> +    *************
> +
> +       [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ...
> +
> +      Comparison operators are:
> +          =   equality
> +          <   less than
> +          >   more than
> +          ~=  masking (valid for IP and Ethernet fields)
> +
> +      Logical operators are:
> +          !{expr}:  NOT
> +          {expr} && {expr}: AND
> +          {expr} || {expr}: OR
> +
> +      Matches and flow metadata:
> +          To compare against a match or info field, use the field directly, e.g:
> +              priority=100
> +              n_bytes>10
> +          Use simple keywords for flags:
> +              tcp and ip_src=192.168.1.1
> +
> +      Actions:
> +          Actions values might be dictionaries, use subkeys to access individual
> +          values, e.g:
> +              output.port=3
> +          Use simple keywords for flags
> +              drop
> +
> +      Examples of valid filters.
> +          nw_addr~=192.168.1.1 && (tcp_dst=80 || tcp_dst=443)
> +          arp=true && !arp_tsa=192.168.1.1
> +          n_bytes>0 && drop=true
> +
> +
> +Example expressions:
> +::
> +
> +   n_bytes > 0 and drop
> +   nw_src~=192.168.1.1 or arp.tsa=192.168.1.1
> +   ! tcp && output.port=2
> +
> +
> +Examples
> +========
> +
> +Print OpenFlow flows sorted by cookie adding OVN data to each one:
> +::
> +
> +    $ ovs-flowviz -i flows.txt openflow cookie --ovn-detrace
> +
> +Print OpenFlow logical structure, showing the flows and heat-map:
> +::
> +
> +    $ ovs-flowviz -i flows.txt openflow logic --show-flows --heat-map
> +
> +Display OpenFlow flows in HTML format with "light" style and highlight drops:
> +::
> +
> +    $ ovs-flowviz -i flows.txt --style "light" --highlight "n_packets > 0 and drop" openflow html > flows.html
> +
> +Display the datapath flows in an interactive graphviz + HTML view:
> +::
> +
> +    $ ovs-flowviz -i flows.txt datapath graph --html > flows.html
> +
> +Display the datapath flow trees that lead to packets being sent to port 10:
> +::
> +
> +    $ ovs-flowviz -i flows.txt --filter "output.port=10" datapath tree
> diff --git a/Documentation/topics/flow-visualization.rst b/Documentation/topics/flow-visualization.rst
> new file mode 100644
> index 000000000..fe4aeacaf
> --- /dev/null
> +++ b/Documentation/topics/flow-visualization.rst
> @@ -0,0 +1,314 @@
> +..
> +      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.
> +
> +      Convention for heading levels in Open vSwitch documentation:
> +
> +      =======  Heading 0 (reserved for the title in a document)
> +      -------  Heading 1
> +      ~~~~~~~  Heading 2
> +      +++++++  Heading 3
> +      '''''''  Heading 4
> +
> +      Avoid deeper levels because they do not render well.
> +
> +==================================
> +Visualizing flows with ovs-flowviz
> +==================================
> +
> +When troubleshooting networking issues with OVS, we typically end up looking
> +at OpenFlow or datapath flow dumps. These dumps tend to be quite dense and
> +difficult to reason about.
> +
> +``ovs-flowviz`` is a utility script that helps visualizing OpenFlow and
> +datapath flows to make it easier to understand what is going on.
> +
> +The `ovs-flowviz(8)`_ manpage describes its basic usage. In this document a few
> +of its advanced visualization formats will be expanded.
> +
> +
> +Installing ovs-flowviz
> +----------------------
> +
> +``ovs-flowviz`` is part of the openvswitch python package but its
> +extra dependencies have to be installed explicitly by running:
> +::
> +
> +    $ pip install openvswitch[flowviz]
> +
> +Or, if you are working with the OVS tree:
> +::
> +
> +    $ cd python && pip install .[flowviz]
> +
> +Visualizing OpenFlow logical block
> +----------------------------------
> +
> +When controllers such as OVN write OpenFlow flows, they typically organize
> +flows in functional blocks. These blocks can expand to multiple flows that
> +"look similar", in the sense that they match on the same fields and have
> +similar actions.
> +
> +However, when we look at a flow dump the number of flows can make it difficult
> +to perceive this logical functionality that the controller is trying to
> +implement using OpenFlow.
> +
> +In this example, we are going to use ``ovs-flowviz openflow logic``
> +visualization to understand an OVN flow dump a bit better.
> +
> +On a particular flow dump we have 23 flows in table 0:
> +::
> +
> +   $ grep -c "table=0" flows.txt
> +   23
> +
> +If we look at the first few lines, the amount of information can be
> +overwhelming and difficult our analysis:
> +
> +::
> +
> +    $ head flows.txt
> +      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,vlan_tci=0x0000/0x1000 actions=conjunction(100,2/2)
> +      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,conj_id=100,in_port="patch-br-int-to",vlan_tci=0x0000/0x1000 actions=load:0xa->NXM_NX_REG13[],load:0xc->NXM_NX_REG11[],load:0xb->NXM_NX_REG12[],load:0xb->OXM_OF_METADATA[],load:0x1->NXM_NX_REG14[],mod_dl_src:02:42:ac:12:00:03,resubmit(,8)
> +      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-6bb3b3-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
> +      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-a6ff98-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
> +      cookie=0xf2ca6195, duration=765.107s, table=0, n_packets=6, n_bytes=636, priority=100,in_port="ovn-k8s-mp0" actions=load:0x1->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0x236e941d, duration=408.874s, table=0, n_packets=11, n_bytes=846, priority=100,in_port=aceac9829941d11 actions=load:0x11->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x3->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0x3facf689, duration=405.581s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="363ba22029cd92b" actions=load:0x12->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x4->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0xe7c8c4bb, duration=405.570s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="6a62cde0d50ef44" actions=load:0x13->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x5->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0x99a0ffc1, duration=59.391s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="5ff3bfaaa4eb622" actions=load:0x14->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x6->NXM_NX_REG14[],resubmit(,8)
> +      cookie=0xe1b5c263, duration=59.365s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="8d9e0bc76347e59" actions=load:0x15->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x7->NXM_NX_REG14[],resubmit(,8)
> +
> +
> +However, we can better understand what table 0 does by looking at its
> +logical representation.
> +::
> +
> +   $ ovs-flowviz -i flows.txt -f "table=0" openflow logic
> +    Ofproto Flows (logical)

'Ofproto Flows' is a strange combination of words.  It should be
'OpenFlow rules' instead.

> +    └── ** TABLE 0 **
> +        ├── priority=180 priority,vlan_tci  --->  conjunction ( x 1 )
> +        ├── priority=180 priority,conj_id,in_port,vlan_tci  --->  load,load,load,load,load,mod_dl_src resubmit(,8), ( x 1 )
> +        ├── priority=100 priority,in_port  --->  move,move,move resubmit(,40), ( x 2 )
> +        ├── priority=100 priority,in_port  --->  load,load,load,load,load resubmit(,8), ( x 16 )
> +        ├── priority=100 priority,in_port,vlan_tci  --->  load,load,load,load,load resubmit(,8), ( x 1 )
> +        ├── priority=100 priority,in_port,dl_vlan  --->  strip_vlan,load,load,load,load,load resubmit(,8), ( x 1 )
> +        └── priority=0 priority  --->   drop, ( x 1 )
> +
> +
> +In only a few logical blocks, we have a good overview of what this table is
> +doing. It looks like it's adding metadata based on input ports and vlan
> +IDs and mainly sending traffic to table 8.
> +
> +Let's look at table 8, an in this case, let's filter out the flows that have
> +not been hit by actual traffic. This is quite easy to do with the arithmetic
> +filtering expressions:
> +::
> +
> +   $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic
> +
> +    Ofproto Flows (logical)
> +    └── ** TABLE 8 **
> +        ├── priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 3 )
> +        └── priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
> +
> +At this point, we might find ourselves a bit lost since we may not remember
> +what metadata OVN stored in the previous table. Here is where
> +``ovs-flowviz``'s OVN integration could come useful. Let's connect to the
> +running OVN instance and ask it about the flows we're looking at.
> +
> +::
> +
> +    $ export OVN_NB_DB=tcp:172.18.0.4:6641
> +    $ export OVN_SB_DB=tcp:172.18.0.4:6642
> +    $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic --ovn-detrace
> +    Ofproto Flows (logical)
> +    └── ** TABLE 8 **
> +        ├── cookie=0xe10c34ee priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> +        │   └── OVN Info
> +        │       ├── *  Logical datapaths:
> +        │       ├── *      "ovn_cluster_router" (366e1c41-0f3d-4420-b796-10692b64e3e4)
> +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtos-ovn-worker2), actions=(xreg0[0..47] = 0a:58:0a:f4:01:01; next;)
> +        │       └── *  Logical Router Port: rtos-ovn-worker2 mac 0a:58:0a:f4:01:01 networks ['10.244.1.1/24'] ipv6_ra_configs {}
> +        ├── cookie=0x11e1adbc priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> +        │   └── OVN Info
> +        │       ├── *  Logical datapaths:
> +        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
> +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
> +        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
> +        ├── cookie=0xf42133f  priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> +        │   └── OVN Info
> +        │       ├── *  Logical datapaths:
> +        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
> +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.dst == 02:42:ac:12:00:03 && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
> +        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
> +        └── cookie=0x43a0327  priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
> +            └── OVN Info
> +                ├── *  Logical datapaths:
> +                ├── *      "ovn-worker" (24280d0b-fee0-4f8e-ba4f-036a9b9af921)
> +                ├── *      "ovn-control-plane" (3262a782-8961-416b-805e-08233e8fda72)
> +                ├── *      "ext_ovn-worker2" (3f88dcd2-c56d-478f-a3b1-c7aee2efe967)
> +                ├── *      "ext_ovn-worker" (5facbaf0-485d-4cf5-8940-eff9678ef7bb)
> +                ├── *      "ext_ovn-control-plane" (8b0aecb6-b05a-48a7-ad09-72524bb91d40)
> +                ├── *      "join" (e2dc230e-2f2a-4b93-93fa-0fe495163514)
> +                ├── *      "ovn-worker2" (f7709fbf-d728-4cff-9b9b-150461cc75d2)
> +                └── *  Logical flow: table=0 (ls_in_check_port_sec), priority=50, match=(1), actions=(reg0[15] = check_in_port_sec(); next;)
> +
> +That's way better. ``ovs-flowviz`` has automatically added the `cookie` to the
> +logical block key so have more blocks but in exchange, it has looked up each
> +cookie on the running OVN databases and inserted the known information on each
> +block. So now we see what OVN is trying to do, the logical flow that generated
> +each OpenFlow flow and the logical datapath each flow belongs to.
> +
> +Visualizing datapath flow trees
> +-------------------------------
> +
> +Now, let's see another typical usecase that can lead to eyestrain:
> +understanding datapath conntrack recirculations.
> +
> +OVS makes heavy use of connection tracking and the ``recirc()`` action
> +to build complex datapaths. Typically, OVS will insert a flow that,
> +when matched, will send the packet through conntrack (using the ``ct`` action)
> +and recirculate it with a particular recirculation id (``recirc_id``). Then, a
> +flow matching on that ``recirc_id`` will be matched and further process the
> +packet. This can happen more than once for a given packet.
> +
> +This sequential set of events is, however, difficult to visualize when you
> +look at a datapath flow dump. Flows are unordered so recirculations need to
> +be followed manually (typically, with heavy use of "grep").
> +
> +For this use-case, ``ovs-flowviz datapath tree`` format can be extremely
> +useful. It builds a hierarchical tree based on the ``recirc_id`` ``in_port``
> +and ``recirc()`` actions.
> +
> +Furthermore, it is common to end up with multiple flows that have the same
> +list of actions. An example of this is a number flows that perform mac/vlan
> +checks for a given port and send the traffic though the same conntrack zone.
> +In order to better visualize this and reduce the amount of duplicated flows
> +that are ineviably printed in this view, these flows are combined into a block
> +and the match keys that are equal for all flows are removed.
> +
> +Here is an example.
> +::
> +
> +  Datapath Flows (logical)
> +  └── ╭────────────────────────────────╮
> +      │ [recirc_id(0x0) in_port(eth0)] │
> +      ╰────────────────────────────────╯
> +      └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +          │ recirc_id(0),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0/0),ct_zone(0/0),ct_mark(0/0),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                                                   │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:01),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.0.0.0/255.255.128.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=32768/0x8000,dst=0/0), packets:711, bytes:114236,                                                                                                         │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660,                                                                                                          │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:22),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:1, bytes:66,                                                                                                          │
> +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:09),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> +          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:0, bytes:0,                                                                                                                 │
> +          ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +          └── ╭───────────────────────────────────────╮
> +              │ actions: ct(zone=32,nat),recirc(0xc1) │
> +              ╰───────────────────────────────────────╯
> +              └── ╭─────────────────────────────────╮
> +                  │ [recirc_id(0xc1) in_port(eth0)] │
> +                  ╰─────────────────────────────────╯
> +                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ip │
> +                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                  │
> +                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +                  │   └── ╭───────────────────────────────────────╮
> +                  │       │ actions: ct(zone=14,nat),recirc(0xc2) │
> +                  │       ╰───────────────────────────────────────╯
> +                  │       └── ╭─────────────────────────────────╮
> +                  │           │ [recirc_id(0xc2) in_port(eth0)] │
> +                  │           ╰─────────────────────────────────╯
> +                  │           └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +                  │               │ recirc_id(0xc2),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0x1),ct_label(0/0),eth(src=00:00:00:00:00:00/00:00:00:00:00:00,dst=00:00:00 │
> +                  │               │ :00:00:00/01:00:00:00:00:00),eth_type(0x0800),ipv4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=0/0,tos=0/0,ttl=0/0,frag=no), packets:4924, bytes:468961,                                        │
> +                  │               ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +                  │               └── ╭──────────────────────╮
> +                  │                   │ actions: ovn-k8s-mp0 │
> +                  │                   ╰──────────────────────╯
> +                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> +                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(0x0800),ip │
> +                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660                                                                          │
> +                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> +

Hrm.  I find this output format very hard to read.  Mst of the spase on screen
is taken by the borders and spaces.  It's hard to correlate matches with the
actions.  I think, at least matches and action should be in the same box,
maybe with an empty line between them.  It would be much easier to understand.
And maybe even not ad an empty line if there is only one match.

Ditching the border entirely may also be an option.

And I'm not sure about printing a common part of the match in a separate box
and still printing that part inside each of the child boxes.  The common one
seems redundant in this case and only takes extra space and increases the
indentation level.

> +
> +The above shows a part of a bigger tree where a first block of flows
> +at ``recirc_id(0)`` that match on different destination ethernet
> +addresses and protocols and send traffic through conntrack (zone 32).
> +
> +Then some additional flows at ``recirc_id(0xc1)`` process each traffic
> +connection independently. One of them, shown in the example, sends the traffic
> +through conntrack zone 14 and after another recirculation the packet is
> +ultimately sent through a port.
> +
> +This is a truly complex multi-zone conntrack pipeline that is now fairly
> +clear thanks to this visualization.
> +
> +Also note, the flows in the block are conveniently sorted by sent packets.
> +
> +This example shows only a single "subtree". Even though the combination of
> +flows with the same action helps, if we use this command to display a big
> +dump, the output can be lengthy. Here are two (combinable) ways to
> +help out.
> +
> +
> +Plotting datapath trees
> +~~~~~~~~~~~~~~~~~~~~~~~
> +
> +By using the ``ovs-flowviz datapath html`` format, long datapath trees can
> +be displayed in an interactive HTML table. The resulting web allows you to
> +collapse and expand subtrees so you can focus on what you're looking for.
> +
> +In addition, the ``ovs-flowviz datapath graph`` format generates a graphviz
> +graph definition where each block of flows with the same ``recirc_id`` match
> +are arranged together and edges are created to represent recirculations.
> +Also, this format comes with further goodies such as displaying the conntrack
> +zones which are key to understanding what the datapath is really doing with a
> +packet.
> +
> +These two formats (``html`` and ``graph``) can even be combined. By using the
> +``ovs-flowviz datapath graph --html`` command, you'll get an interactive
> +HTML table alongside a `svg` graphical representation of the flows. Click on
> +a flow on the svg and it'll take you to the corresponding entry in the
> +flow table.
> +
> +
> +Filtering
> +~~~~~~~~~
> +
> +Apart from being able to expand and collapse subtrees, we can use filtering.
> +
> +However, filtering works in a slightly different way compared with OpenFlow
> +flows. Instead of just removing non-matching flows, the output
> +of a filtered datapath flow tree will show full sub-trees that contain at
> +least one flow that satisfies the filter.
> +
> +For example, let's take the flows in the above example, and let's imagine we
> +want to understand what traffic is going out on port ``ovn-k8s-mp0``. We
> +could run the tool as:
> +::
> +
> +   $ ovs-appctl dpctl/dump-flows | ovs-flowviz -f "output.port=ovn-k8s-mp0" datapath tree
> +
> +The resulting flow tree will contain all of the flows above, even those
> +with ``recirc_id(0)`` and ``recirc_id(0xc1)`` that don't actually output
> +traffic to port ``ovn-k8s-mp0``. Why? because they are all part of a subtree
> +that contains flows that do output packets on port ``ovn-k8s-mp0``
> +
> +That way, we see the "full picture" of how traffic on ending up in a particular
> +port is being processed.
> +
> +.. _ovs-flowviz(8): https://docs.openvswitch.org/en/latest/ref/ovs-flowviz.8
> diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst
> index f239fcf83..9ddb145dd 100644
> --- a/Documentation/topics/index.rst
> +++ b/Documentation/topics/index.rst
> @@ -58,3 +58,4 @@ OVS
>     userspace-checksum-offloading
>     userspace-tx-steering
>     usdt-probes
> +   flow-visualization
> diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
> index 94b6d7431..d228ef1d3 100644
> --- a/rhel/openvswitch-fedora.spec.in
> +++ b/rhel/openvswitch-fedora.spec.in
> @@ -499,6 +499,7 @@ fi
>  %{_mandir}/man8/ovs-ctl.8*
>  %{_mandir}/man8/ovs-dpctl.8*
>  %{_mandir}/man8/ovs-dpctl-top.8*
> +%{_mandir}/man8/ovs-flowviz.8*
>  %{_mandir}/man8/ovs-kmod-ctl.8*
>  %{_mandir}/man8/ovs-ofctl.8*
>  %{_mandir}/man8/ovs-pki.8*
> diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
> index 9903dd10a..437e01ee1 100644
> --- a/rhel/openvswitch.spec.in
> +++ b/rhel/openvswitch.spec.in
> @@ -223,6 +223,7 @@ exit 0
>  /usr/share/man/man8/ovs-ctl.8.gz
>  /usr/share/man/man8/ovs-dpctl.8.gz
>  /usr/share/man/man8/ovs-dpctl-top.8.gz
> +/usr/share/man/man8/ovs-flowviz.8.gz
>  /usr/share/man/man8/ovs-kmod-ctl.8.gz
>  /usr/share/man/man8/ovs-ofctl.8.gz
>  /usr/share/man/man8/ovs-parse-backtrace.8.gz
Simon Horman Aug. 20, 2024, 2:48 p.m. UTC | #3
On Fri, Aug 16, 2024 at 03:26:12PM +0200, Eelco Chaudron wrote:
> 
> 
> On 10 Jul 2024, at 19:05, Adrian Moreno wrote:
> 
> > Add a man page for ovs-flowviz as well as a topic page with some more
> > detailed examples.
> >
> > Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
> 
> 
> Thanks for sending the v5. See some spelling suggestions below. Included Simon as a more native speaker for any more suggestions ;)

My high school English teachers would get a chuckle out of that :)

I've been over the text. And the gist of my suggestions are around moving
the text to the imperative mood.

I started making suggestions without that, but the results were not, IMHO, very natural. Which brings me to my disclaimer. This is a very subjective topic.
And I have no doubt made my own errors. So please take my suggestions as
just that; they are just what I think is best.

> 
> //Eelco
> 
> > ---
> >  Documentation/automake.mk                   |   4 +-
> >  Documentation/conf.py                       |   2 +
> >  Documentation/ref/index.rst                 |   1 +
> >  Documentation/ref/ovs-flowviz.8.rst         | 535 ++++++++++++++++++++
> >  Documentation/topics/flow-visualization.rst | 314 ++++++++++++
> >  Documentation/topics/index.rst              |   1 +
> >  rhel/openvswitch-fedora.spec.in             |   1 +
> >  rhel/openvswitch.spec.in                    |   1 +
> >  8 files changed, 858 insertions(+), 1 deletion(-)
> >  create mode 100644 Documentation/ref/ovs-flowviz.8.rst
> >  create mode 100644 Documentation/topics/flow-visualization.rst
> >
> > diff --git a/Documentation/automake.mk b/Documentation/automake.mk
> > index 47d2e336a..539870aa2 100644
> > --- a/Documentation/automake.mk
> > +++ b/Documentation/automake.mk
> > @@ -45,7 +45,7 @@ DOC_SOURCE = \
> >  	Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
> >  	Documentation/topics/fuzzing/ovs-fuzzers.rst \
> >  	Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
> > -	Documentation/topics/testing.rst \
> > +	Documentation/topics/flow-visualization.rst \
> >  	Documentation/topics/integration.rst \
> >  	Documentation/topics/language-bindings.rst \
> >  	Documentation/topics/networking-namespaces.rst \
> > @@ -55,6 +55,7 @@ DOC_SOURCE = \
> >  	Documentation/topics/ovsdb-replication.rst \
> >  	Documentation/topics/porting.rst \
> >  	Documentation/topics/record-replay.rst \
> > +	Documentation/topics/testing.rst \
> >  	Documentation/topics/tracing.rst \
> >  	Documentation/topics/usdt-probes.rst \
> >  	Documentation/topics/userspace-checksum-offloading.rst \
> > @@ -162,6 +163,7 @@ RST_MANPAGES = \
> >  	ovs-actions.7.rst \
> >  	ovs-appctl.8.rst \
> >  	ovs-ctl.8.rst \
> > +	ovs-flowviz.8.rst \
> >  	ovs-l3ping.8.rst \
> >  	ovs-parse-backtrace.8.rst \
> >  	ovs-pki.8.rst \
> > diff --git a/Documentation/conf.py b/Documentation/conf.py
> > index 15785605a..3a82f23a7 100644
> > --- a/Documentation/conf.py
> > +++ b/Documentation/conf.py
> > @@ -120,6 +120,8 @@ _man_pages = [
> >       u'utility for configuring running Open vSwitch daemons'),
> >      ('ovs-ctl.8',
> >       u'OVS startup helper script'),
> > +    ('ovs-flowviz.8',
> > +     u'utility for visualizing OpenFlow and datapath flows'),
> >      ('ovs-l3ping.8',
> >       u'check network deployment for L3 tunneling problems'),
> >      ('ovs-parse-backtrace.8',
> > diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
> > index 03ada932f..7f2fe6177 100644
> > --- a/Documentation/ref/index.rst
> > +++ b/Documentation/ref/index.rst
> > @@ -42,6 +42,7 @@ time:
> >     ovs-actions.7
> >     ovs-appctl.8
> >     ovs-ctl.8
> > +   ovs-flowviz.8
> >     ovs-l3ping.8
> >     ovs-pki.8
> >     ovs-sim.1
> > diff --git a/Documentation/ref/ovs-flowviz.8.rst b/Documentation/ref/ovs-flowviz.8.rst
> > new file mode 100644
> > index 000000000..969fda9be
> > --- /dev/null
> > +++ b/Documentation/ref/ovs-flowviz.8.rst
> > @@ -0,0 +1,535 @@
> > +..
> > +      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.
> > +
> > +      Convention for heading levels in Open vSwitch documentation:
> > +
> > +      =======  Heading 0 (reserved for the title in a document)
> > +      -------  Heading 1
> > +      ~~~~~~~  Heading 2
> > +      +++++++  Heading 3
> > +      '''''''  Heading 4
> > +
> > +      Avoid deeper levels because they do not render well.
> > +
> > +===========
> > +ovs-flowviz
> > +===========
> > +
> > +Synopsis
> > +========
> > +
> > +``ovs-flowviz``
> > +[``[-i | --input] <[alias,]file>``]
> > +[``[-c | --config] <file>``]
> > +[``[-f | --filter] <filter>``]
> > +[``[-h | --highlight] <filter>``]
> > +[``--style <style>``]
> > +*<flow_type>* *<format>* [<arg>...]
> > +
> > +``ovs-flowviz --help``
> > +
> > +Description
> > +===========
> > +
> > +The ``ovs-flowviz`` program helps visualize OpenFlow and datapath flow dumps

The ``ovs-flowviz`` program -> ``ovs-flowviz``

> > +in different formats in order to make them more easily understood.
> > +
> > +The program works by reading flows from ``stdin`` or from a file specified

The program works by reading -> ``ovs-flowviz``

> > +in the ``--input`` option, filtering them, highlighting them, and finally

in the -> by the

> > +outputting them in one of the predefined formats.
> > +
> > +
> > +Options
> > +=======
> > +
> > +.. program: ovs-flowviz
> > +
> > +.. option:: -h, --help
> > +
> > +    Prints a brief help message to the console.
> > +
> > +.. option:: -i <[alias,]file>, --input <[alias,]file>
> > +
> > +    Specifies the file to read flows from. If not provided, ``ovs-flowviz``
> > +    will read flows from stdin.
> > +
> > +    This option can be specified multiple times.
> > +    The file path can prepended by an alias that will be shown in the output.
> > +    For example: ``--input node1,/path/to/file1 --input node2,/path/to/file2``
> > +
> > +.. option:: -c <file>, --config <file>
> > +
> > +    Specifies the style configuration file to use. ``ovs-flowviz`` ships with
> > +    a default configuration file but it can be overridden using this option.

	Specifies the style configuration file to use, overriding the default.

> > +    Styles defined in the style configuration file will be select-able using

will be select-able -> can be selected

> > +    the ``--style`` option.
> > +
> > +    For more details on the style configuration file, see

see -> see the

> > +    `Style Configuration File`_ section below.
> > +
> > +.. option:: -f <filter>, --filter <filter>
> > +
> > +   Tells ``ovs-flowviz`` to filter the flows and only show the ones that match
> > +   the expression (although some formats implement filtering differently,
> > +   see `Datapath tree format`_ below).

Flow filter expression. Only those flows matching the expression will be
shown (although some formats implement filtering differently, see `Datapath
tree format`_ below).

> > +
> > +   The filtering syntax is detailed in `Filtering Syntax`_.
> > +
> > +.. option:: -h <filter>, --highlight <filter>
> > +
> > +   Tells ``ovs-flowviz`` to highlight the flows that match the provided filter

Tells ``ovs-flowviz`` to highlight -> Highlight
provided filter -> filter expression

> > +
> > +   The filtering syntax is detailed in `Filtering Syntax`_.
> > +
> > +.. option:: --style <style>
> > +
> > +   Specifies the style to use. The style must have been defined in the
> > +   style configuration file.

Specifies the style -> Style
have been -> be

> > +
> > +.. option:: <flow_type>
> > +
> > +   "openflow" or "datapath".
> > +
> > +.. option:: <format>
> > +
> > +   See `Supported formats`_ section.

See -> See the

> > +
> > +
> > +Supported formats
> > +=================
> > +
> > +``ovs-flowviz`` supports several visualization formats for both OpenFlow and
> > +datapath flows that are summarized in the following table:

datapath flows that are summarized in the following table: -> datapath flows:

> > +
> > +.. list-table::
> > +   :widths: 20 10 70
> > +   :align: center
> > +   :header-rows: 1
> > +
> > +   * - Flow Type
> > +     - Format
> > +     - Description
> > +   * - Both
> > +     - console
> > +     - Prints the flows in a configurable, colorful style in the console.
> > +   * - Both
> > +     - json
> > +     - Prints the flows in JSON format.
> > +   * - Both
> > +     - html
> > +     - Prints the flows in an HTML list.
> > +   * - Openflow
> > +     - cookie
> > +     - Prints the flows in the console sorted by cookie.
> > +   * - Openflow
> > +     - logic
> > +     - Prints the logical structure of flows in the console.
> > +   * - Datapath
> > +     - tree
> > +     - Prints the flows a tree structure arranged by `recirc_id`.
> 
> Prints the flows as a tree structure arranged by `recirc_id`
> 
> > +   * - Datapath
> > +     - graph
> > +     - Prints a graphviz graph of the flows arranged by `recirc_id`.
> > +
> > +
> > +Console format
> > +~~~~~~~~~~~~~~
> > +
> > +The ``console`` works for both OpenFlow and datapath flow types and prints
> > +flows in the terminal with the style determined by the ``--style`` option.

The ``console`` format works for both OpenFlow and datapath flow types, and
prints flows in the terminal using the style selected by the ``--style`` option.

> > +
> > +Additionally, it accepts the following arguments:

Additionally, it accepts the following arguments: -> Arguments:

> > +
> > +.. option:: -h, --heat-map
> > +
> > +   This option changes the color of the packet and byte counters to reflect

This option changes the color -> Color (or Colour :)

> > +   their relative size. The color gradient goes through the following colors:
> > +   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
> > +
> > +   Note filtering is applied before the range is calculated.
> > +
> > +
> > +JSON format
> > +~~~~~~~~~~~
> > +
> > +The ``json`` format works for both OpenFlow and datapath flow types and prints

flows and -> flows, and

> > +flows in JSON format. See `JSON Syntax`_ for more details.
> > +
> > +
> > +HTML format
> > +~~~~~~~~~~~
> > +
> > +The ``html`` format works for both OpenFlow and datapath flows and prints

flows and -> flows, and

> > +flows in an HTML table that offers some basic interactivity. OpenFlow flows
> > +are sorted in tables and datapath flows are arranged in flow trees
> > +(see `Datapath tree format`_ for more details).
> > +
> > +Styles defined via Style Configuration File and selected via ``--style`` option
> > +also apply to ``html`` format.

to ``html``` -> to the ``html``

> > +
> > +
> > +OpenFlow cookie format
> > +~~~~~~~~~~~~~~~~~~~~~~
> > +
> > +The OpenFlow ``cookie`` format is similar to the ``console`` format but
> > +instead of arranging the flows per table, it arranges the flows per cookie.

I'm unsure, but perhaps: per -> by (x2)

> > +
> > +
> > +Openflow logic format
> > +~~~~~~~~~~~~~~~~~~~~~
> > +
> > +The OpenFlow ``logic`` format helps visualize the logic structure of OpenFlow
> > +pipelines by arranging flows into *logical blocks*.
> > +A logical block is a set of flows that have:
> > +
> > +* Same ``priority``.
> > +* Match on the same fields (regardless of the match value and mask).
> > +* Execute the same actions (regardless of the actions' arguments,
> > +  except for resubmit and output).
> > +* Optionally, the ``cookie`` can be counted as part of the logical flow.

counted -> included

> > +
> > +This format supports the following extra arguments:

Arguments:

> > +
> > +.. option:: -s, --show-flows
> > +
> > +    Show all the flows under each logical block.
> > +
> > +.. option:: -d, --ovn-detrace
> > +
> > +    Use ovn-detrace.py script to extract cookie information (implies '-c').
> > +
> > +.. option:: -c, --cookie
> > +
> > +    Consider the cookie in the logical block.
> > +
> > +.. option:: --ovn-detrace-path <path>
> > +
> > +    Use an alternative path to look for ovn_detrace.py script.


look for ovn_detrace.py script -> search for ovn_detrace.py

> > +
> > +.. option:: --ovnnb-db text
> > +
> > +   Specify the OVN NB database string (implies '-d').
> > +   Default value is "unix:/var/run/ovn/ovnnb_db.sock".

maybe: s/Specify the //
       s/Default value is/Default:/

Likewise below.

> > +
> > +.. option:: --ovnsb-db text
> > +
> > +   Specify the OVN SB database string (implies '-d').
> > +   Default value is "unix:/var/run/ovn/ovnsb_db.sock".
> > +
> > +.. option:: --o <text>, --ovn-filter <text>
> > +
> > +   Specify the a filter to be run on the ovn-detrace information.
> 
> Specify the a filter -> Specify the filter
> 
> > +   Syntax: python regular expression
> > +   (See https://docs.python.org/3/library/re.html).
> > +
> > +.. option:: -h, --heat-map
> > +
> > +   This option changes the color of the packet and byte counters to reflect

maybe: This option changes -> Change

> > +   their relative size. The color gradient goes through the following colors:
> > +   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
> > +
> > +   Note filtering is applied before the range is calculated.
> > +
> > +
> > +Datapath tree format
> > +~~~~~~~~~~~~~~~~~~~~
> > +
> > +The datapath ``tree`` format arranges datapath flows in a hierarchical tree.
> > +The tree is comprised of blocks with the same `recirc_id` and `in_port`.
> > +Within those blocks, flows with the same action are combined and matches
> 
> are combined, and matches

combined, and -> combined. And
> 
> > +which are the same are omitted to try to reduce the visual noise.

s/try to //

> > +
> > +When an flow's actions includes the `recirc()` action with a specific

an -> a

> > +`recirc_id`, flows matching on that `recirc_id` and `in_port` are listed

and `in_port` -> and the same `in_port`

> > +below. This is done recursively for all actions.
> > +
> > +The result is a hierarchical representation that helps understand how actions

helps understand -> shows

> > +are related to each other via recirculation. Note that flows with a specific
> > +non-zero `recirc_id` are listed below each group of flows that have a
> > +corresponding `recirc()` action.
> > +Therefore, the output contains duplicated flows and can be lengthy.

lengthy -> verbose

> > +
> > +Also, filtering works in a slightly different way for datapath flow trees.

Also, filtering -> filtering

> > +Unlike other formats where a filter simply removes non-matching flows,
> > +the output of a filtered datapath flow tree will show full sub-trees
> > +that contain at least one flow that satisfies the filter.
> > +
> > +The ``html`` format prints this same tree in an interactive HTML table and

in -> as

> > +the ``graph`` format shows the same tree in a graphviz graph.

Ditto.

> > +
> > +
> > +Datapath graph format
> > +~~~~~~~~~~~~~~~~~~~~~
> > +
> > +The datapath ``graph`` generates a graphviz visual representation of the
> > +same tree-like flow hierarchy that the ``tree`` format prints.

that the ``tree`` format prints -> as the ``tree`` format.

> > +
> > +It supports the following extra argument:

Arguments:

> > +
> > +.. option:: -h, --html
> > +
> > +    Prints the graphviz format in an svg image alongside the interactive HTML

Prints -> Print

> > +    table of flows (that 'html' format would print).

    Print graphviz format as an SVG image alongside an interactive HTML
    table of flows.

> > +
> > +
> > +JSON Syntax
> > +===========
> > +
> > +Both OpenFlow and datapath `json` formats print a JSON list of JSON
> > +objects each of one representing an individual flow.
> 
> objects each of one, remove ‘of’.

Maybe: objects, each representing an individual flow.

> 
> > +
> > +Each flow object contains the following keys:

contains -> includes

> > +
> > +**orig**
> > +    Contains the original flow string.

         Original flow string.

> > +
> > +
> > +**info**
> > +   Contains an object with the flow information

        Object with flow information

> > +   such as: cookie, duration, table, n_packets, n_bytes, etc.
> > +
> > +
> > +**match**
> > +   Contains an object with the flow match.

        Object with flow match

> > +   For each match, the object contains a key-value where the key is the name
> > +   of the match as defined in ovs-fields and ovs-ofctl and the value

        ... in ovs-fields and ovs-ofctl, and the value

> > +   represents the match value. The way each value is represented depends on its
> > +   type. See `Value representation`_.
> > +
> > +
> > +**actions**
> > +   Contains a list of action objects.

        List of action objects.

> > +   Each action is represented by an JSON object that has one key and one value.
> > +   The key corresponds to the action name. The value represents the arguments
> > +   of such key. See `Action representation`_.

maybe: such -> the

> > +
> > +
> > +**ufid**
> > +   (datapath flows only) Contains the ufid.

	(datapath flows only) The UFID

> > +
> > +
> > +Value representation
> > +~~~~~~~~~~~~~~~~~~~~
> > +
> > +Values are represented differently depending on their type:
> > +
> > +* Flags: Fields that represent flags (e.g: tcp) are represented by boolean

TCP

> > +  "true"
> > +
> > +* Decimal / Hexadecimal: They are represented by their integer value.

They are represented -> Represented

Likewise below (x3)

> > +  If they support masking, they are represented by a dictionary with two keys:
> > +  value contains the field value and mask contains the mask. Both are integers.
> > +
> > +* Ethernet: They are represented by a string: {address}[/{mask}]
> > +
> > +* IPv4 / IPv6: They are represented by a string {address}[/mask]
> > +
> > +* Registers: They are represented by a dictionary with three keys:
> > +  field contains the field value (string), start and end that represent the
> 
> “(string), start and end that” -> “(string), start, and end that”
> 
> > +  first and last bit of the register.
> > +
> > +For example, the register
> > +::
> > +
> > +
> > +   NXM_NX_REG10[0..15]
> > +
> > +
> > +is represented as
> > +::
> > +
> > +
> > +   {
> > +       "field": "NXM_NX_REG10",
> > +       "start": 0,
> > +       "end": 15
> > +   },
> > +
> > +
> > +Action representation
> > +~~~~~~~~~~~~~~~~~~~~~
> > +
> > +Actions are generally represented by an object that has a single key and a

and a -> and

> > +value. The key is the action name as defined ovs-actions.
> > +
> > +The value of actions that have no arguments (such as ``drop``) is
> > +(boolean) ``true``.
> > +
> > +The value of actions that have a list of arguments (e.g:
> > +``resubmit([port],[table],[ct])``) is an object that has the name of the
> > +argument as key. The argument names for each action is defined in
> > +ovs-actions. For example, the action
> > +::
> > +
> > +   resubmit(,10)
> > +
> > +is represented as
> > +::
> > +
> > +   {
> > +       "redirect": {
> > +           "port": "",
> > +           "table": 10
> > +       }
> > +   }
> > +
> > +The value of actions that have a key-word list as arguments
> > +(e.g: ``ct([argument])``) is an object whose keys correspond to the keys
> > +defined in ``ovs-actions(7)``. The way values are represented depends
> > +on the type of the argument.
> > +For example, the action
> > +::
> > +
> > +   ct(table=14,zone=NXM_NX_REG12[0..15],nat)
> > +
> > +is represented as
> > +::
> > +
> > +   {
> > +       "ct": {
> > +           "table": 14,
> > +           "zone": {
> > +               "field": "NXM_NX_REG12",
> > +               "start": 0,
> > +               "end": 15
> > +           },
> > +           "nat": true
> > +       }
> > +   }
> > +
> > +
> > +Style Configuration File
> > +========================
> > +
> > +The style configuration file that can be selected via the ``--config`` option

that can be -> is

> > +has INI syntax and can define any number of styles to be used by both

has INI syntax and can -> and has INI syntax. It can

> > +``console`` and ``html`` formats. Once defined in the configuration file
> > +they can be selected using the ``--style`` option.

they can be -> formats are

> > +
> > +INI sections are used to define styles, ``[styles.mystyle]`` defines a style
> > +called `mystle`. Within a section styles can be defined as:
> > +
> > +::
> > +
> > +     [FORMAT].[PORTION].[SELECTOR].[ELEMENT] = [VALUE]
> > +
> > +
> > +**FORMAT**
> > +   Either ``console`` or ``html``
> > +
> > +**PORTION**
> > +   The part of the a key-value the style applies to. It can be:
> 
> The part of the a key-value the style applies to. ->
> The part of the key-value the style applies to.

Also, s/The part/Part/ ; s/It can be://

> 
> > +   ``key`` (to indicate the key part of a key-value), ``value`` (to indicate
> > +   the value part of a key-value), ``flag`` (to indicate a single flag)
> > +   or ``delim`` (to indicate delimiters such as parentheses, brackets, etc).

Maybe no parentheses above?

> > +
> > +**SELECTOR**
> > +   Is used to select what key-value the style applies to. It can be:

Is used to select what -> Select the
s/It can be://

> > +   ``highlighted`` (to indicate highlighted key-values), ``type.<type>``

Maybe no parentheses above?

> > +   to indicate certain types such as `IPAddress` or `EthMask` or `<keyname>`
> > +   to select a particular key name.
> > +
> > +**ELEMENT**
> > +   Is used to select what style element to modify. It can be one

Is used to select -> Select
s/It can be one of: //

> > +   of: **color** or **underline** (only for **console** format).
> > +
> > +**VALUE**
> > +   Is either a color hex, other color names defined in the rich python
> 
> Is either a color hex, other color names defined in the rich python ->
> Is either a color hex, or color name defined in the rich python

Also, Is either -> Either

> 
> > +   library (https://rich.readthedocs.io/en/stable/appendix/colors.html) or
> > +   "true" if the element is ``underline``.
> > +
> > +A default configuration file is shipped with the tool and its path is printed

the tool -> ``ovs-flowviz``

> > +in the ``--help`` output. A detailed description of the syntax alongside
> > +some examples are available there.
> > +
> > +
> > +Filtering syntax
> > +================
> > +
> > +``ovs-flowviz`` provides rich highlighting and filtering. The special command
> > +``ovs-flowviz filter`` dumps the filtering syntax:
> > +
> > +::
> > +
> > +    $ ovs-flowviz filter
> > +    Filter Syntax
> > +    *************
> > +
> > +       [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ...
> > +
> > +      Comparison operators are:

s/ are//

> > +          =   equality
> > +          <   less than
> > +          >   more than
> > +          ~=  masking (valid for IP and Ethernet fields)
> > +
> > +      Logical operators are:
> > +          !{expr}:  NOT

Ditto.

> > +          {expr} && {expr}: AND
> > +          {expr} || {expr}: OR
> > +
> > +      Matches and flow metadata:
> > +          To compare against a match or info field, use the field directly, e.g:
> > +              priority=100
> > +              n_bytes>10
> > +          Use simple keywords for flags:
> > +              tcp and ip_src=192.168.1.1
> > +
> > +      Actions:
> > +          Actions values might be dictionaries, use subkeys to access individual
> > +          values, e.g:
> > +              output.port=3
> > +          Use simple keywords for flags
> > +              drop
> > +
> > +      Examples of valid filters.

s/./:/

> > +          nw_addr~=192.168.1.1 && (tcp_dst=80 || tcp_dst=443)
> > +          arp=true && !arp_tsa=192.168.1.1
> > +          n_bytes>0 && drop=true
> > +
> > +
> > +Example expressions:
> > +::
> > +
> > +   n_bytes > 0 and drop
> > +   nw_src~=192.168.1.1 or arp.tsa=192.168.1.1
> > +   ! tcp && output.port=2
> > +
> > +
> > +Examples
> > +========
> > +
> > +Print OpenFlow flows sorted by cookie adding OVN data to each one:
> > +::
> > +
> > +    $ ovs-flowviz -i flows.txt openflow cookie --ovn-detrace
> > +
> > +Print OpenFlow logical structure, showing the flows and heat-map:
> > +::
> > +
> > +    $ ovs-flowviz -i flows.txt openflow logic --show-flows --heat-map
> > +
> > +Display OpenFlow flows in HTML format with "light" style and highlight drops:
> > +::
> > +
> > +    $ ovs-flowviz -i flows.txt --style "light" --highlight "n_packets > 0 and drop" openflow html > flows.html
> > +
> > +Display the datapath flows in an interactive graphviz + HTML view:
> > +::
> > +
> > +    $ ovs-flowviz -i flows.txt datapath graph --html > flows.html
> > +
> > +Display the datapath flow trees that lead to packets being sent to port 10:
> > +::
> > +
> > +    $ ovs-flowviz -i flows.txt --filter "output.port=10" datapath tree
> > diff --git a/Documentation/topics/flow-visualization.rst b/Documentation/topics/flow-visualization.rst
> > new file mode 100644
> > index 000000000..fe4aeacaf
> > --- /dev/null
> > +++ b/Documentation/topics/flow-visualization.rst
> > @@ -0,0 +1,314 @@
> > +..
> > +      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.
> > +
> > +      Convention for heading levels in Open vSwitch documentation:
> > +
> > +      =======  Heading 0 (reserved for the title in a document)
> > +      -------  Heading 1
> > +      ~~~~~~~  Heading 2
> > +      +++++++  Heading 3
> > +      '''''''  Heading 4
> > +
> > +      Avoid deeper levels because they do not render well.
> > +
> > +==================================
> > +Visualizing flows with ovs-flowviz
> > +==================================
> > +
> > +When troubleshooting networking issues with OVS, we typically end up looking
> > +at OpenFlow or datapath flow dumps. These dumps tend to be quite dense and
> > +difficult to reason about.
> > +
> > +``ovs-flowviz`` is a utility script that helps visualizing OpenFlow and
> 
> visualizing -> to visualize
> 
> > +datapath flows to make it easier to understand what is going on.
> > +
> > +The `ovs-flowviz(8)`_ manpage describes its basic usage. In this document a few
> > +of its advanced visualization formats will be expanded.
> > +
> > +
> > +Installing ovs-flowviz
> > +----------------------
> > +
> > +``ovs-flowviz`` is part of the openvswitch python package but its
> > +extra dependencies have to be installed explicitly by running:
> > +::
> > +
> > +    $ pip install openvswitch[flowviz]
> > +
> > +Or, if you are working with the OVS tree:
> > +::
> > +
> > +    $ cd python && pip install .[flowviz]
> > +
> > +Visualizing OpenFlow logical block
> > +----------------------------------
> > +
> > +When controllers such as OVN write OpenFlow flows, they typically organize
> > +flows in functional blocks. These blocks can expand to multiple flows that
> > +"look similar", in the sense that they match on the same fields and have
> > +similar actions.
> > +
> > +However, when we look at a flow dump the number of flows can make it difficult

we look at -> looking at

> > +to perceive this logical functionality that the controller is trying to
> > +implement using OpenFlow.
> > +
> > +In this example, we are going to use ``ovs-flowviz openflow logic``
> > +visualization to understand an OVN flow dump a bit better.

``ovs-flowviz openflow logic`` visualization can be used ...

> > +
> > +On a particular flow dump we have 23 flows in table 0:
> > +::
> > +
> > +   $ grep -c "table=0" flows.txt
> > +   23
> > +
> > +If we look at the first few lines, the amount of information can be

If we look at -> Looking at

> > +overwhelming and difficult our analysis:
> > +
> > +::
> > +
> > +    $ head flows.txt
> > +      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,vlan_tci=0x0000/0x1000 actions=conjunction(100,2/2)
> > +      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,conj_id=100,in_port="patch-br-int-to",vlan_tci=0x0000/0x1000 actions=load:0xa->NXM_NX_REG13[],load:0xc->NXM_NX_REG11[],load:0xb->NXM_NX_REG12[],load:0xb->OXM_OF_METADATA[],load:0x1->NXM_NX_REG14[],mod_dl_src:02:42:ac:12:00:03,resubmit(,8)
> > +      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-6bb3b3-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
> > +      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-a6ff98-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
> > +      cookie=0xf2ca6195, duration=765.107s, table=0, n_packets=6, n_bytes=636, priority=100,in_port="ovn-k8s-mp0" actions=load:0x1->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],resubmit(,8)
> > +      cookie=0x236e941d, duration=408.874s, table=0, n_packets=11, n_bytes=846, priority=100,in_port=aceac9829941d11 actions=load:0x11->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x3->NXM_NX_REG14[],resubmit(,8)
> > +      cookie=0x3facf689, duration=405.581s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="363ba22029cd92b" actions=load:0x12->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x4->NXM_NX_REG14[],resubmit(,8)
> > +      cookie=0xe7c8c4bb, duration=405.570s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="6a62cde0d50ef44" actions=load:0x13->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x5->NXM_NX_REG14[],resubmit(,8)
> > +      cookie=0x99a0ffc1, duration=59.391s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="5ff3bfaaa4eb622" actions=load:0x14->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x6->NXM_NX_REG14[],resubmit(,8)
> > +      cookie=0xe1b5c263, duration=59.365s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="8d9e0bc76347e59" actions=load:0x15->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x7->NXM_NX_REG14[],resubmit(,8)
> > +
> > +
> > +However, we can better understand what table 0 does by looking at its
> > +logical representation.
> > +::
> > +
> > +   $ ovs-flowviz -i flows.txt -f "table=0" openflow logic
> > +    Ofproto Flows (logical)
> > +    └── ** TABLE 0 **
> > +        ├── priority=180 priority,vlan_tci  --->  conjunction ( x 1 )
> > +        ├── priority=180 priority,conj_id,in_port,vlan_tci  --->  load,load,load,load,load,mod_dl_src resubmit(,8), ( x 1 )
> > +        ├── priority=100 priority,in_port  --->  move,move,move resubmit(,40), ( x 2 )
> > +        ├── priority=100 priority,in_port  --->  load,load,load,load,load resubmit(,8), ( x 16 )
> > +        ├── priority=100 priority,in_port,vlan_tci  --->  load,load,load,load,load resubmit(,8), ( x 1 )
> > +        ├── priority=100 priority,in_port,dl_vlan  --->  strip_vlan,load,load,load,load,load resubmit(,8), ( x 1 )
> > +        └── priority=0 priority  --->   drop, ( x 1 )
> > +
> > +
> > +In only a few logical blocks, we have a good overview of what this table is
> > +doing. It looks like it's adding metadata based on input ports and vlan
> > +IDs and mainly sending traffic to table 8.

we have a good -> there is a good

> > +
> > +Let's look at table 8, an in this case, let's filter out the flows that have
> 
> Let's look at table 8, an in this case, ->
> Let's look at table 8, and in this case,

Look at table 8, and in this case, filter out flows that have...

> 
> > +not been hit by actual traffic. This is quite easy to do with the arithmetic
> > +filtering expressions:
> > +::
> > +
> > +   $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic
> > +
> > +    Ofproto Flows (logical)
> > +    └── ** TABLE 8 **
> > +        ├── priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 3 )
> > +        └── priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
> > +
> > +At this point, we might find ourselves a bit lost since we may not remember
> > +what metadata OVN stored in the previous table. Here is where
> > +``ovs-flowviz``'s OVN integration could come useful. Let's connect to the
> 
> OVN integration could come useful. ->
> OVN integration could be useful.
> 
> > +running OVN instance and ask it about the flows we're looking at.

At this point, understanding the output may be difficult without relating
it to metadata OVN stored in the previous table. This is where
``ovs-flowviz``'s OVN integration is useful.

Connect to the running OVN instance and ask it about the flows we're looking at.

> > +
> > +::
> > +
> > +    $ export OVN_NB_DB=tcp:172.18.0.4:6641
> > +    $ export OVN_SB_DB=tcp:172.18.0.4:6642
> > +    $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic --ovn-detrace
> > +    Ofproto Flows (logical)
> > +    └── ** TABLE 8 **
> > +        ├── cookie=0xe10c34ee priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> > +        │   └── OVN Info
> > +        │       ├── *  Logical datapaths:
> > +        │       ├── *      "ovn_cluster_router" (366e1c41-0f3d-4420-b796-10692b64e3e4)
> > +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtos-ovn-worker2), actions=(xreg0[0..47] = 0a:58:0a:f4:01:01; next;)
> > +        │       └── *  Logical Router Port: rtos-ovn-worker2 mac 0a:58:0a:f4:01:01 networks ['10.244.1.1/24'] ipv6_ra_configs {}
> > +        ├── cookie=0x11e1adbc priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> > +        │   └── OVN Info
> > +        │       ├── *  Logical datapaths:
> > +        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
> > +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
> > +        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
> > +        ├── cookie=0xf42133f  priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
> > +        │   └── OVN Info
> > +        │       ├── *  Logical datapaths:
> > +        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
> > +        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.dst == 02:42:ac:12:00:03 && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
> > +        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
> > +        └── cookie=0x43a0327  priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
> > +            └── OVN Info
> > +                ├── *  Logical datapaths:
> > +                ├── *      "ovn-worker" (24280d0b-fee0-4f8e-ba4f-036a9b9af921)
> > +                ├── *      "ovn-control-plane" (3262a782-8961-416b-805e-08233e8fda72)
> > +                ├── *      "ext_ovn-worker2" (3f88dcd2-c56d-478f-a3b1-c7aee2efe967)
> > +                ├── *      "ext_ovn-worker" (5facbaf0-485d-4cf5-8940-eff9678ef7bb)
> > +                ├── *      "ext_ovn-control-plane" (8b0aecb6-b05a-48a7-ad09-72524bb91d40)
> > +                ├── *      "join" (e2dc230e-2f2a-4b93-93fa-0fe495163514)
> > +                ├── *      "ovn-worker2" (f7709fbf-d728-4cff-9b9b-150461cc75d2)
> > +                └── *  Logical flow: table=0 (ls_in_check_port_sec), priority=50, match=(1), actions=(reg0[15] = check_in_port_sec(); next;)
> > +
> > +That's way better. ``ovs-flowviz`` has automatically added the `cookie` to the

s/That's way better.//

> > +logical block key so have more blocks but in exchange, it has looked up each
> > +cookie on the running OVN databases and inserted the known information on each
> 
> inserted -> insert

Actually, I think inserted may be correct.

> 
> > +block. So now we see what OVN is trying to do, the logical flow that generated

So now we see what OVN is trying to do, -> What OVN is doing is now clearer,

> > +each OpenFlow flow and the logical datapath each flow belongs to.
> > +
> > +Visualizing datapath flow trees
> > +-------------------------------
> > +
> > +Now, let's see another typical usecase that can lead to eyestrain:

s/let's see//

> > +understanding datapath conntrack recirculations.
> > +
> > +OVS makes heavy use of connection tracking and the ``recirc()`` action
> > +to build complex datapaths. Typically, OVS will insert a flow that,
> > +when matched, will send the packet through conntrack (using the ``ct`` action)
> > +and recirculate it with a particular recirculation id (``recirc_id``). Then, a
> > +flow matching on that ``recirc_id`` will be matched and further process the

flow -> flows

> > +packet. This can happen more than once for a given packet.
> > +
> > +This sequential set of events is, however, difficult to visualize when you
> > +look at a datapath flow dump. Flows are unordered so recirculations need to
> > +be followed manually (typically, with heavy use of "grep").
> > +
> > +For this use-case, ``ovs-flowviz datapath tree`` format can be extremely
> > +useful. It builds a hierarchical tree based on the ``recirc_id`` ``in_port``

``recirc_id`` ``in_port` -> ``recirc_id``, ``in_port`

> > +and ``recirc()`` actions.
> > +
> > +Furthermore, it is common to end up with multiple flows that have the same
> > +list of actions. An example of this is a number flows that perform mac/vlan
> > +checks for a given port and send the traffic though the same conntrack zone.
> > +In order to better visualize this and reduce the amount of duplicated flows
> > +that are ineviably printed in this view, these flows are combined into a block

block -> block,

> 
> Not sure what you wanted to type here, so I guess; ineviably -> inevitably

maybe just drop ineviably ?

> 
> > +and the match keys that are equal for all flows are removed.
> > +
> > +Here is an example.

For example:

> > +::
> > +
> > +  Datapath Flows (logical)
> > +  └── ╭────────────────────────────────╮
> > +      │ [recirc_id(0x0) in_port(eth0)] │
> > +      ╰────────────────────────────────╯
> > +      └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> > +          │ recirc_id(0),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0/0),ct_zone(0/0),ct_mark(0/0),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ipv4(src=10.132.0.7,dst=1 │
> > +          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                                                   │
> > +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:01),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> > +          │ 0.0.0.0/255.255.128.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=32768/0x8000,dst=0/0), packets:711, bytes:114236,                                                                                                         │
> > +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> > +          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660,                                                                                                          │
> > +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:22),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> > +          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:1, bytes:66,                                                                                                          │
> > +          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:09),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
> > +          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:0, bytes:0,                                                                                                                 │
> > +          ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> > +          └── ╭───────────────────────────────────────╮
> > +              │ actions: ct(zone=32,nat),recirc(0xc1) │
> > +              ╰───────────────────────────────────────╯
> > +              └── ╭─────────────────────────────────╮
> > +                  │ [recirc_id(0xc1) in_port(eth0)] │
> > +                  ╰─────────────────────────────────╯
> > +                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> > +                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ip │
> > +                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                  │
> > +                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> > +                  │   └── ╭───────────────────────────────────────╮
> > +                  │       │ actions: ct(zone=14,nat),recirc(0xc2) │
> > +                  │       ╰───────────────────────────────────────╯
> > +                  │       └── ╭─────────────────────────────────╮
> > +                  │           │ [recirc_id(0xc2) in_port(eth0)] │
> > +                  │           ╰─────────────────────────────────╯
> > +                  │           └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> > +                  │               │ recirc_id(0xc2),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0x1),ct_label(0/0),eth(src=00:00:00:00:00:00/00:00:00:00:00:00,dst=00:00:00 │
> > +                  │               │ :00:00:00/01:00:00:00:00:00),eth_type(0x0800),ipv4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=0/0,tos=0/0,ttl=0/0,frag=no), packets:4924, bytes:468961,                                        │
> > +                  │               ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> > +                  │               └── ╭──────────────────────╮
> > +                  │                   │ actions: ovn-k8s-mp0 │
> > +                  │                   ╰──────────────────────╯
> > +                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
> > +                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(0x0800),ip │
> > +                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660                                                                          │
> > +                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> > +
> > +
> > +The above shows a part of a bigger tree where a first block of flows
> > +at ``recirc_id(0)`` that match on different destination ethernet
> > +addresses and protocols and send traffic through conntrack (zone 32).
> 
> send -> sends

The above shows a part of a bigger tree with an initial block of flows
at ``recirc_id(0)`` which match on different destination Ethernet
addresses and protocols, and send traffic through conntrack (zone 32).

> > +
> > +Then some additional flows at ``recirc_id(0xc1)`` process each traffic

s/traffic//

> > +connection independently. One of them, shown in the example, sends the traffic

s/traffic/packets/

> > +through conntrack zone 14 and after another recirculation the packet is

14 -> 14,

> > +ultimately sent through a port.
> > +
> > +This is a truly complex multi-zone conntrack pipeline that is now fairly
> > +clear thanks to this visualization.

maybe: fairly clear -> significantly clearer

> > +
> > +Also note, the flows in the block are conveniently sorted by sent packets.
> > +
> > +This example shows only a single "subtree". Even though the combination of
> > +flows with the same action helps, if we use this command to display a big

big -> large

> > +dump, the output can be lengthy. Here are two (combinable) ways to

lengthy -> verbose

> > +help out.

There are two, combinable, mechanisms that can help.

> > +
> > +
> > +Plotting datapath trees
> > +~~~~~~~~~~~~~~~~~~~~~~~
> > +
> > +By using the ``ovs-flowviz datapath html`` format, long datapath trees can
> > +be displayed in an interactive HTML table. The resulting web allows you to
> > +collapse and expand subtrees so you can focus on what you're looking for.

The resulting web page allows subtrees to be expanded and collapsed,
allowing focus on the desired information.

> > +
> > +In addition, the ``ovs-flowviz datapath graph`` format generates a graphviz

In addition, the -> The

> > +graph definition where each block of flows with the same ``recirc_id`` match

each block -> blocks

> > +are arranged together and edges are created to represent recirculations.

together -> together,

> > +Also, this format comes with further goodies such as displaying the conntrack

Also, this -> This
goodies -> features

> > +zones which are key to understanding what the datapath is really doing with a

zones -> zones,

> > +packet.
> > +
> > +These two formats (``html`` and ``graph``) can even be combined. By using the
> > +``ovs-flowviz datapath graph --html`` command, you'll get an interactive
> > +HTML table alongside a `svg` graphical representation of the flows. Click on
> > +a flow on the svg and it'll take you to the corresponding entry in the
> > +flow table.

The ``html`` and ``graph`` can also be combined.  The ``ovs-flowviz
datapath graph --html`` command will output an interactive  HTML table
alongside a SVG graphical representation of the flows. Flow in the SVG
representation link to to to the corresponding entry in the HTML table.

> > +
> > +
> > +Filtering
> > +~~~~~~~~~
> > +
> > +Apart from being able to expand and collapse subtrees, we can use filtering.

As well as allowing expanding and collapsing of subtrees, filtering can be
used.

> > +
> > +However, filtering works in a slightly different way compared with OpenFlow

compared -> than it does

> > +flows. Instead of just removing non-matching flows, the output
> > +of a filtered datapath flow tree will show full sub-trees that contain at

contain -> containing

> > +least one flow that satisfies the filter.
> > +
> > +For example, let's take the flows in the above example, and let's imagine we
> > +want to understand what traffic is going out on port ``ovn-k8s-mp0``. We
> > +could run the tool as:

For example the following command allows understanding the flows in the
above example in the context of traffic is going out on port
``ovn-k8s-mp0``.

> > +::
> > +
> > +   $ ovs-appctl dpctl/dump-flows | ovs-flowviz -f "output.port=ovn-k8s-mp0" datapath tree
> > +
> > +The resulting flow tree will contain all of the flows above, even those

even -> including

> > +with ``recirc_id(0)`` and ``recirc_id(0xc1)`` that don't actually output
> > +traffic to port ``ovn-k8s-mp0``. Why? because they are all part of a subtree

Why? -> This is
s/are all //

> > +that contains flows that do output packets on port ``ovn-k8s-mp0``

s/do //

> > +
> > +That way, we see the "full picture" of how traffic on ending up in a particular
> 
> on ending -> ends
> 
> > +port is being processed.

This provides a "full picture" of how traffic, ending on a particular port,
is being processed.

> > +
> > +.. _ovs-flowviz(8): https://docs.openvswitch.org/en/latest/ref/ovs-flowviz.8
> > diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst
> > index f239fcf83..9ddb145dd 100644
> > --- a/Documentation/topics/index.rst
> > +++ b/Documentation/topics/index.rst
> > @@ -58,3 +58,4 @@ OVS
> >     userspace-checksum-offloading
> >     userspace-tx-steering
> >     usdt-probes
> > +   flow-visualization
> > diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
> > index 94b6d7431..d228ef1d3 100644
> > --- a/rhel/openvswitch-fedora.spec.in
> > +++ b/rhel/openvswitch-fedora.spec.in
> > @@ -499,6 +499,7 @@ fi
> >  %{_mandir}/man8/ovs-ctl.8*
> >  %{_mandir}/man8/ovs-dpctl.8*
> >  %{_mandir}/man8/ovs-dpctl-top.8*
> > +%{_mandir}/man8/ovs-flowviz.8*
> >  %{_mandir}/man8/ovs-kmod-ctl.8*
> >  %{_mandir}/man8/ovs-ofctl.8*
> >  %{_mandir}/man8/ovs-pki.8*
> > diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
> > index 9903dd10a..437e01ee1 100644
> > --- a/rhel/openvswitch.spec.in
> > +++ b/rhel/openvswitch.spec.in
> > @@ -223,6 +223,7 @@ exit 0
> >  /usr/share/man/man8/ovs-ctl.8.gz
> >  /usr/share/man/man8/ovs-dpctl.8.gz
> >  /usr/share/man/man8/ovs-dpctl-top.8.gz
> > +/usr/share/man/man8/ovs-flowviz.8.gz
> >  /usr/share/man/man8/ovs-kmod-ctl.8.gz
> >  /usr/share/man/man8/ovs-ofctl.8.gz
> >  /usr/share/man/man8/ovs-parse-backtrace.8.gz
> > -- 
> > 2.45.2
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 47d2e336a..539870aa2 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -45,7 +45,7 @@  DOC_SOURCE = \
 	Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \
 	Documentation/topics/fuzzing/ovs-fuzzers.rst \
 	Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \
-	Documentation/topics/testing.rst \
+	Documentation/topics/flow-visualization.rst \
 	Documentation/topics/integration.rst \
 	Documentation/topics/language-bindings.rst \
 	Documentation/topics/networking-namespaces.rst \
@@ -55,6 +55,7 @@  DOC_SOURCE = \
 	Documentation/topics/ovsdb-replication.rst \
 	Documentation/topics/porting.rst \
 	Documentation/topics/record-replay.rst \
+	Documentation/topics/testing.rst \
 	Documentation/topics/tracing.rst \
 	Documentation/topics/usdt-probes.rst \
 	Documentation/topics/userspace-checksum-offloading.rst \
@@ -162,6 +163,7 @@  RST_MANPAGES = \
 	ovs-actions.7.rst \
 	ovs-appctl.8.rst \
 	ovs-ctl.8.rst \
+	ovs-flowviz.8.rst \
 	ovs-l3ping.8.rst \
 	ovs-parse-backtrace.8.rst \
 	ovs-pki.8.rst \
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 15785605a..3a82f23a7 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -120,6 +120,8 @@  _man_pages = [
      u'utility for configuring running Open vSwitch daemons'),
     ('ovs-ctl.8',
      u'OVS startup helper script'),
+    ('ovs-flowviz.8',
+     u'utility for visualizing OpenFlow and datapath flows'),
     ('ovs-l3ping.8',
      u'check network deployment for L3 tunneling problems'),
     ('ovs-parse-backtrace.8',
diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index 03ada932f..7f2fe6177 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -42,6 +42,7 @@  time:
    ovs-actions.7
    ovs-appctl.8
    ovs-ctl.8
+   ovs-flowviz.8
    ovs-l3ping.8
    ovs-pki.8
    ovs-sim.1
diff --git a/Documentation/ref/ovs-flowviz.8.rst b/Documentation/ref/ovs-flowviz.8.rst
new file mode 100644
index 000000000..969fda9be
--- /dev/null
+++ b/Documentation/ref/ovs-flowviz.8.rst
@@ -0,0 +1,535 @@ 
+..
+      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.
+
+      Convention for heading levels in Open vSwitch documentation:
+
+      =======  Heading 0 (reserved for the title in a document)
+      -------  Heading 1
+      ~~~~~~~  Heading 2
+      +++++++  Heading 3
+      '''''''  Heading 4
+
+      Avoid deeper levels because they do not render well.
+
+===========
+ovs-flowviz
+===========
+
+Synopsis
+========
+
+``ovs-flowviz``
+[``[-i | --input] <[alias,]file>``]
+[``[-c | --config] <file>``]
+[``[-f | --filter] <filter>``]
+[``[-h | --highlight] <filter>``]
+[``--style <style>``]
+*<flow_type>* *<format>* [<arg>...]
+
+``ovs-flowviz --help``
+
+Description
+===========
+
+The ``ovs-flowviz`` program helps visualize OpenFlow and datapath flow dumps
+in different formats in order to make them more easily understood.
+
+The program works by reading flows from ``stdin`` or from a file specified
+in the ``--input`` option, filtering them, highlighting them, and finally
+outputting them in one of the predefined formats.
+
+
+Options
+=======
+
+.. program: ovs-flowviz
+
+.. option:: -h, --help
+
+    Prints a brief help message to the console.
+
+.. option:: -i <[alias,]file>, --input <[alias,]file>
+
+    Specifies the file to read flows from. If not provided, ``ovs-flowviz``
+    will read flows from stdin.
+
+    This option can be specified multiple times.
+    The file path can prepended by an alias that will be shown in the output.
+    For example: ``--input node1,/path/to/file1 --input node2,/path/to/file2``
+
+.. option:: -c <file>, --config <file>
+
+    Specifies the style configuration file to use. ``ovs-flowviz`` ships with
+    a default configuration file but it can be overridden using this option.
+    Styles defined in the style configuration file will be select-able using
+    the ``--style`` option.
+
+    For more details on the style configuration file, see
+    `Style Configuration File`_ section below.
+
+.. option:: -f <filter>, --filter <filter>
+
+   Tells ``ovs-flowviz`` to filter the flows and only show the ones that match
+   the expression (although some formats implement filtering differently,
+   see `Datapath tree format`_ below).
+
+   The filtering syntax is detailed in `Filtering Syntax`_.
+
+.. option:: -h <filter>, --highlight <filter>
+
+   Tells ``ovs-flowviz`` to highlight the flows that match the provided filter
+
+   The filtering syntax is detailed in `Filtering Syntax`_.
+
+.. option:: --style <style>
+
+   Specifies the style to use. The style must have been defined in the
+   style configuration file.
+
+.. option:: <flow_type>
+
+   "openflow" or "datapath".
+
+.. option:: <format>
+
+   See `Supported formats`_ section.
+
+
+Supported formats
+=================
+
+``ovs-flowviz`` supports several visualization formats for both OpenFlow and
+datapath flows that are summarized in the following table:
+
+.. list-table::
+   :widths: 20 10 70
+   :align: center
+   :header-rows: 1
+
+   * - Flow Type
+     - Format
+     - Description
+   * - Both
+     - console
+     - Prints the flows in a configurable, colorful style in the console.
+   * - Both
+     - json
+     - Prints the flows in JSON format.
+   * - Both
+     - html
+     - Prints the flows in an HTML list.
+   * - Openflow
+     - cookie
+     - Prints the flows in the console sorted by cookie.
+   * - Openflow
+     - logic
+     - Prints the logical structure of flows in the console.
+   * - Datapath
+     - tree
+     - Prints the flows a tree structure arranged by `recirc_id`.
+   * - Datapath
+     - graph
+     - Prints a graphviz graph of the flows arranged by `recirc_id`.
+
+
+Console format
+~~~~~~~~~~~~~~
+
+The ``console`` works for both OpenFlow and datapath flow types and prints
+flows in the terminal with the style determined by the ``--style`` option.
+
+Additionally, it accepts the following arguments:
+
+.. option:: -h, --heat-map
+
+   This option changes the color of the packet and byte counters to reflect
+   their relative size. The color gradient goes through the following colors:
+   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
+
+   Note filtering is applied before the range is calculated.
+
+
+JSON format
+~~~~~~~~~~~
+
+The ``json`` format works for both OpenFlow and datapath flow types and prints
+flows in JSON format. See `JSON Syntax`_ for more details.
+
+
+HTML format
+~~~~~~~~~~~
+
+The ``html`` format works for both OpenFlow and datapath flows and prints
+flows in an HTML table that offers some basic interactivity. OpenFlow flows
+are sorted in tables and datapath flows are arranged in flow trees
+(see `Datapath tree format`_ for more details).
+
+Styles defined via Style Configuration File and selected via ``--style`` option
+also apply to ``html`` format.
+
+
+OpenFlow cookie format
+~~~~~~~~~~~~~~~~~~~~~~
+
+The OpenFlow ``cookie`` format is similar to the ``console`` format but
+instead of arranging the flows per table, it arranges the flows per cookie.
+
+
+Openflow logic format
+~~~~~~~~~~~~~~~~~~~~~
+
+The OpenFlow ``logic`` format helps visualize the logic structure of OpenFlow
+pipelines by arranging flows into *logical blocks*.
+A logical block is a set of flows that have:
+
+* Same ``priority``.
+* Match on the same fields (regardless of the match value and mask).
+* Execute the same actions (regardless of the actions' arguments,
+  except for resubmit and output).
+* Optionally, the ``cookie`` can be counted as part of the logical flow.
+
+This format supports the following extra arguments:
+
+.. option:: -s, --show-flows
+
+    Show all the flows under each logical block.
+
+.. option:: -d, --ovn-detrace
+
+    Use ovn-detrace.py script to extract cookie information (implies '-c').
+
+.. option:: -c, --cookie
+
+    Consider the cookie in the logical block.
+
+.. option:: --ovn-detrace-path <path>
+
+    Use an alternative path to look for ovn_detrace.py script.
+
+.. option:: --ovnnb-db text
+
+   Specify the OVN NB database string (implies '-d').
+   Default value is "unix:/var/run/ovn/ovnnb_db.sock".
+
+.. option:: --ovnsb-db text
+
+   Specify the OVN SB database string (implies '-d').
+   Default value is "unix:/var/run/ovn/ovnsb_db.sock".
+
+.. option:: --o <text>, --ovn-filter <text>
+
+   Specify the a filter to be run on the ovn-detrace information.
+   Syntax: python regular expression
+   (See https://docs.python.org/3/library/re.html).
+
+.. option:: -h, --heat-map
+
+   This option changes the color of the packet and byte counters to reflect
+   their relative size. The color gradient goes through the following colors:
+   blue (coldest, lowest), cyan, green, yellow, red (hottest, highest)
+
+   Note filtering is applied before the range is calculated.
+
+
+Datapath tree format
+~~~~~~~~~~~~~~~~~~~~
+
+The datapath ``tree`` format arranges datapath flows in a hierarchical tree.
+The tree is comprised of blocks with the same `recirc_id` and `in_port`.
+Within those blocks, flows with the same action are combined and matches
+which are the same are omitted to try to reduce the visual noise.
+
+When an flow's actions includes the `recirc()` action with a specific
+`recirc_id`, flows matching on that `recirc_id` and `in_port` are listed
+below. This is done recursively for all actions.
+
+The result is a hierarchical representation that helps understand how actions
+are related to each other via recirculation. Note that flows with a specific
+non-zero `recirc_id` are listed below each group of flows that have a
+corresponding `recirc()` action.
+Therefore, the output contains duplicated flows and can be lengthy.
+
+Also, filtering works in a slightly different way for datapath flow trees.
+Unlike other formats where a filter simply removes non-matching flows,
+the output of a filtered datapath flow tree will show full sub-trees
+that contain at least one flow that satisfies the filter.
+
+The ``html`` format prints this same tree in an interactive HTML table and
+the ``graph`` format shows the same tree in a graphviz graph.
+
+
+Datapath graph format
+~~~~~~~~~~~~~~~~~~~~~
+
+The datapath ``graph`` generates a graphviz visual representation of the
+same tree-like flow hierarchy that the ``tree`` format prints.
+
+It supports the following extra argument:
+
+.. option:: -h, --html
+
+    Prints the graphviz format in an svg image alongside the interactive HTML
+    table of flows (that 'html' format would print).
+
+
+JSON Syntax
+===========
+
+Both OpenFlow and datapath `json` formats print a JSON list of JSON
+objects each of one representing an individual flow.
+
+Each flow object contains the following keys:
+
+**orig**
+    Contains the original flow string.
+
+
+**info**
+   Contains an object with the flow information
+   such as: cookie, duration, table, n_packets, n_bytes, etc.
+
+
+**match**
+   Contains an object with the flow match.
+   For each match, the object contains a key-value where the key is the name
+   of the match as defined in ovs-fields and ovs-ofctl and the value
+   represents the match value. The way each value is represented depends on its
+   type. See `Value representation`_.
+
+
+**actions**
+   Contains a list of action objects.
+   Each action is represented by an JSON object that has one key and one value.
+   The key corresponds to the action name. The value represents the arguments
+   of such key. See `Action representation`_.
+
+
+**ufid**
+   (datapath flows only) Contains the ufid.
+
+
+Value representation
+~~~~~~~~~~~~~~~~~~~~
+
+Values are represented differently depending on their type:
+
+* Flags: Fields that represent flags (e.g: tcp) are represented by boolean
+  "true"
+
+* Decimal / Hexadecimal: They are represented by their integer value.
+  If they support masking, they are represented by a dictionary with two keys:
+  value contains the field value and mask contains the mask. Both are integers.
+
+* Ethernet: They are represented by a string: {address}[/{mask}]
+
+* IPv4 / IPv6: They are represented by a string {address}[/mask]
+
+* Registers: They are represented by a dictionary with three keys:
+  field contains the field value (string), start and end that represent the
+  first and last bit of the register.
+
+For example, the register
+::
+
+
+   NXM_NX_REG10[0..15]
+
+
+is represented as
+::
+
+
+   {
+       "field": "NXM_NX_REG10",
+       "start": 0,
+       "end": 15
+   },
+
+
+Action representation
+~~~~~~~~~~~~~~~~~~~~~
+
+Actions are generally represented by an object that has a single key and a
+value. The key is the action name as defined ovs-actions.
+
+The value of actions that have no arguments (such as ``drop``) is
+(boolean) ``true``.
+
+The value of actions that have a list of arguments (e.g:
+``resubmit([port],[table],[ct])``) is an object that has the name of the
+argument as key. The argument names for each action is defined in
+ovs-actions. For example, the action
+::
+
+   resubmit(,10)
+
+is represented as
+::
+
+   {
+       "redirect": {
+           "port": "",
+           "table": 10
+       }
+   }
+
+The value of actions that have a key-word list as arguments
+(e.g: ``ct([argument])``) is an object whose keys correspond to the keys
+defined in ``ovs-actions(7)``. The way values are represented depends
+on the type of the argument.
+For example, the action
+::
+
+   ct(table=14,zone=NXM_NX_REG12[0..15],nat)
+
+is represented as
+::
+
+   {
+       "ct": {
+           "table": 14,
+           "zone": {
+               "field": "NXM_NX_REG12",
+               "start": 0,
+               "end": 15
+           },
+           "nat": true
+       }
+   }
+
+
+Style Configuration File
+========================
+
+The style configuration file that can be selected via the ``--config`` option
+has INI syntax and can define any number of styles to be used by both
+``console`` and ``html`` formats. Once defined in the configuration file
+they can be selected using the ``--style`` option.
+
+INI sections are used to define styles, ``[styles.mystyle]`` defines a style
+called `mystle`. Within a section styles can be defined as:
+
+::
+
+     [FORMAT].[PORTION].[SELECTOR].[ELEMENT] = [VALUE]
+
+
+**FORMAT**
+   Either ``console`` or ``html``
+
+**PORTION**
+   The part of the a key-value the style applies to. It can be:
+   ``key`` (to indicate the key part of a key-value), ``value`` (to indicate
+   the value part of a key-value), ``flag`` (to indicate a single flag)
+   or ``delim`` (to indicate delimiters such as parentheses, brackets, etc).
+
+**SELECTOR**
+   Is used to select what key-value the style applies to. It can be:
+   ``highlighted`` (to indicate highlighted key-values), ``type.<type>``
+   to indicate certain types such as `IPAddress` or `EthMask` or `<keyname>`
+   to select a particular key name.
+
+**ELEMENT**
+   Is used to select what style element to modify. It can be one
+   of: **color** or **underline** (only for **console** format).
+
+**VALUE**
+   Is either a color hex, other color names defined in the rich python
+   library (https://rich.readthedocs.io/en/stable/appendix/colors.html) or
+   "true" if the element is ``underline``.
+
+A default configuration file is shipped with the tool and its path is printed
+in the ``--help`` output. A detailed description of the syntax alongside
+some examples are available there.
+
+
+Filtering syntax
+================
+
+``ovs-flowviz`` provides rich highlighting and filtering. The special command
+``ovs-flowviz filter`` dumps the filtering syntax:
+
+::
+
+    $ ovs-flowviz filter
+    Filter Syntax
+    *************
+
+       [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ...
+
+      Comparison operators are:
+          =   equality
+          <   less than
+          >   more than
+          ~=  masking (valid for IP and Ethernet fields)
+
+      Logical operators are:
+          !{expr}:  NOT
+          {expr} && {expr}: AND
+          {expr} || {expr}: OR
+
+      Matches and flow metadata:
+          To compare against a match or info field, use the field directly, e.g:
+              priority=100
+              n_bytes>10
+          Use simple keywords for flags:
+              tcp and ip_src=192.168.1.1
+
+      Actions:
+          Actions values might be dictionaries, use subkeys to access individual
+          values, e.g:
+              output.port=3
+          Use simple keywords for flags
+              drop
+
+      Examples of valid filters.
+          nw_addr~=192.168.1.1 && (tcp_dst=80 || tcp_dst=443)
+          arp=true && !arp_tsa=192.168.1.1
+          n_bytes>0 && drop=true
+
+
+Example expressions:
+::
+
+   n_bytes > 0 and drop
+   nw_src~=192.168.1.1 or arp.tsa=192.168.1.1
+   ! tcp && output.port=2
+
+
+Examples
+========
+
+Print OpenFlow flows sorted by cookie adding OVN data to each one:
+::
+
+    $ ovs-flowviz -i flows.txt openflow cookie --ovn-detrace
+
+Print OpenFlow logical structure, showing the flows and heat-map:
+::
+
+    $ ovs-flowviz -i flows.txt openflow logic --show-flows --heat-map
+
+Display OpenFlow flows in HTML format with "light" style and highlight drops:
+::
+
+    $ ovs-flowviz -i flows.txt --style "light" --highlight "n_packets > 0 and drop" openflow html > flows.html
+
+Display the datapath flows in an interactive graphviz + HTML view:
+::
+
+    $ ovs-flowviz -i flows.txt datapath graph --html > flows.html
+
+Display the datapath flow trees that lead to packets being sent to port 10:
+::
+
+    $ ovs-flowviz -i flows.txt --filter "output.port=10" datapath tree
diff --git a/Documentation/topics/flow-visualization.rst b/Documentation/topics/flow-visualization.rst
new file mode 100644
index 000000000..fe4aeacaf
--- /dev/null
+++ b/Documentation/topics/flow-visualization.rst
@@ -0,0 +1,314 @@ 
+..
+      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.
+
+      Convention for heading levels in Open vSwitch documentation:
+
+      =======  Heading 0 (reserved for the title in a document)
+      -------  Heading 1
+      ~~~~~~~  Heading 2
+      +++++++  Heading 3
+      '''''''  Heading 4
+
+      Avoid deeper levels because they do not render well.
+
+==================================
+Visualizing flows with ovs-flowviz
+==================================
+
+When troubleshooting networking issues with OVS, we typically end up looking
+at OpenFlow or datapath flow dumps. These dumps tend to be quite dense and
+difficult to reason about.
+
+``ovs-flowviz`` is a utility script that helps visualizing OpenFlow and
+datapath flows to make it easier to understand what is going on.
+
+The `ovs-flowviz(8)`_ manpage describes its basic usage. In this document a few
+of its advanced visualization formats will be expanded.
+
+
+Installing ovs-flowviz
+----------------------
+
+``ovs-flowviz`` is part of the openvswitch python package but its
+extra dependencies have to be installed explicitly by running:
+::
+
+    $ pip install openvswitch[flowviz]
+
+Or, if you are working with the OVS tree:
+::
+
+    $ cd python && pip install .[flowviz]
+
+Visualizing OpenFlow logical block
+----------------------------------
+
+When controllers such as OVN write OpenFlow flows, they typically organize
+flows in functional blocks. These blocks can expand to multiple flows that
+"look similar", in the sense that they match on the same fields and have
+similar actions.
+
+However, when we look at a flow dump the number of flows can make it difficult
+to perceive this logical functionality that the controller is trying to
+implement using OpenFlow.
+
+In this example, we are going to use ``ovs-flowviz openflow logic``
+visualization to understand an OVN flow dump a bit better.
+
+On a particular flow dump we have 23 flows in table 0:
+::
+
+   $ grep -c "table=0" flows.txt
+   23
+
+If we look at the first few lines, the amount of information can be
+overwhelming and difficult our analysis:
+
+::
+
+    $ head flows.txt
+      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,vlan_tci=0x0000/0x1000 actions=conjunction(100,2/2)
+      cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,conj_id=100,in_port="patch-br-int-to",vlan_tci=0x0000/0x1000 actions=load:0xa->NXM_NX_REG13[],load:0xc->NXM_NX_REG11[],load:0xb->NXM_NX_REG12[],load:0xb->OXM_OF_METADATA[],load:0x1->NXM_NX_REG14[],mod_dl_src:02:42:ac:12:00:03,resubmit(,8)
+      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-6bb3b3-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
+      cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-a6ff98-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40)
+      cookie=0xf2ca6195, duration=765.107s, table=0, n_packets=6, n_bytes=636, priority=100,in_port="ovn-k8s-mp0" actions=load:0x1->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],resubmit(,8)
+      cookie=0x236e941d, duration=408.874s, table=0, n_packets=11, n_bytes=846, priority=100,in_port=aceac9829941d11 actions=load:0x11->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x3->NXM_NX_REG14[],resubmit(,8)
+      cookie=0x3facf689, duration=405.581s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="363ba22029cd92b" actions=load:0x12->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x4->NXM_NX_REG14[],resubmit(,8)
+      cookie=0xe7c8c4bb, duration=405.570s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="6a62cde0d50ef44" actions=load:0x13->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x5->NXM_NX_REG14[],resubmit(,8)
+      cookie=0x99a0ffc1, duration=59.391s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="5ff3bfaaa4eb622" actions=load:0x14->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x6->NXM_NX_REG14[],resubmit(,8)
+      cookie=0xe1b5c263, duration=59.365s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="8d9e0bc76347e59" actions=load:0x15->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x7->NXM_NX_REG14[],resubmit(,8)
+
+
+However, we can better understand what table 0 does by looking at its
+logical representation.
+::
+
+   $ ovs-flowviz -i flows.txt -f "table=0" openflow logic
+    Ofproto Flows (logical)
+    └── ** TABLE 0 **
+        ├── priority=180 priority,vlan_tci  --->  conjunction ( x 1 )
+        ├── priority=180 priority,conj_id,in_port,vlan_tci  --->  load,load,load,load,load,mod_dl_src resubmit(,8), ( x 1 )
+        ├── priority=100 priority,in_port  --->  move,move,move resubmit(,40), ( x 2 )
+        ├── priority=100 priority,in_port  --->  load,load,load,load,load resubmit(,8), ( x 16 )
+        ├── priority=100 priority,in_port,vlan_tci  --->  load,load,load,load,load resubmit(,8), ( x 1 )
+        ├── priority=100 priority,in_port,dl_vlan  --->  strip_vlan,load,load,load,load,load resubmit(,8), ( x 1 )
+        └── priority=0 priority  --->   drop, ( x 1 )
+
+
+In only a few logical blocks, we have a good overview of what this table is
+doing. It looks like it's adding metadata based on input ports and vlan
+IDs and mainly sending traffic to table 8.
+
+Let's look at table 8, an in this case, let's filter out the flows that have
+not been hit by actual traffic. This is quite easy to do with the arithmetic
+filtering expressions:
+::
+
+   $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic
+
+    Ofproto Flows (logical)
+    └── ** TABLE 8 **
+        ├── priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 3 )
+        └── priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
+
+At this point, we might find ourselves a bit lost since we may not remember
+what metadata OVN stored in the previous table. Here is where
+``ovs-flowviz``'s OVN integration could come useful. Let's connect to the
+running OVN instance and ask it about the flows we're looking at.
+
+::
+
+    $ export OVN_NB_DB=tcp:172.18.0.4:6641
+    $ export OVN_SB_DB=tcp:172.18.0.4:6642
+    $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic --ovn-detrace
+    Ofproto Flows (logical)
+    └── ** TABLE 8 **
+        ├── cookie=0xe10c34ee priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
+        │   └── OVN Info
+        │       ├── *  Logical datapaths:
+        │       ├── *      "ovn_cluster_router" (366e1c41-0f3d-4420-b796-10692b64e3e4)
+        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtos-ovn-worker2), actions=(xreg0[0..47] = 0a:58:0a:f4:01:01; next;)
+        │       └── *  Logical Router Port: rtos-ovn-worker2 mac 0a:58:0a:f4:01:01 networks ['10.244.1.1/24'] ipv6_ra_configs {}
+        ├── cookie=0x11e1adbc priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
+        │   └── OVN Info
+        │       ├── *  Logical datapaths:
+        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
+        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
+        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
+        ├── cookie=0xf42133f  priority=50 priority,reg14,metadata,dl_dst  --->  load resubmit(,9), ( x 1 )
+        │   └── OVN Info
+        │       ├── *  Logical datapaths:
+        │       ├── *      "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56)
+        │       ├── *  Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.dst == 02:42:ac:12:00:03 && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;)
+        │       └── *  Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {}
+        └── cookie=0x43a0327  priority=50 priority,metadata  --->  load,move resubmit(,73),resubmit(,9), ( x 2 )
+            └── OVN Info
+                ├── *  Logical datapaths:
+                ├── *      "ovn-worker" (24280d0b-fee0-4f8e-ba4f-036a9b9af921)
+                ├── *      "ovn-control-plane" (3262a782-8961-416b-805e-08233e8fda72)
+                ├── *      "ext_ovn-worker2" (3f88dcd2-c56d-478f-a3b1-c7aee2efe967)
+                ├── *      "ext_ovn-worker" (5facbaf0-485d-4cf5-8940-eff9678ef7bb)
+                ├── *      "ext_ovn-control-plane" (8b0aecb6-b05a-48a7-ad09-72524bb91d40)
+                ├── *      "join" (e2dc230e-2f2a-4b93-93fa-0fe495163514)
+                ├── *      "ovn-worker2" (f7709fbf-d728-4cff-9b9b-150461cc75d2)
+                └── *  Logical flow: table=0 (ls_in_check_port_sec), priority=50, match=(1), actions=(reg0[15] = check_in_port_sec(); next;)
+
+That's way better. ``ovs-flowviz`` has automatically added the `cookie` to the
+logical block key so have more blocks but in exchange, it has looked up each
+cookie on the running OVN databases and inserted the known information on each
+block. So now we see what OVN is trying to do, the logical flow that generated
+each OpenFlow flow and the logical datapath each flow belongs to.
+
+Visualizing datapath flow trees
+-------------------------------
+
+Now, let's see another typical usecase that can lead to eyestrain:
+understanding datapath conntrack recirculations.
+
+OVS makes heavy use of connection tracking and the ``recirc()`` action
+to build complex datapaths. Typically, OVS will insert a flow that,
+when matched, will send the packet through conntrack (using the ``ct`` action)
+and recirculate it with a particular recirculation id (``recirc_id``). Then, a
+flow matching on that ``recirc_id`` will be matched and further process the
+packet. This can happen more than once for a given packet.
+
+This sequential set of events is, however, difficult to visualize when you
+look at a datapath flow dump. Flows are unordered so recirculations need to
+be followed manually (typically, with heavy use of "grep").
+
+For this use-case, ``ovs-flowviz datapath tree`` format can be extremely
+useful. It builds a hierarchical tree based on the ``recirc_id`` ``in_port``
+and ``recirc()`` actions.
+
+Furthermore, it is common to end up with multiple flows that have the same
+list of actions. An example of this is a number flows that perform mac/vlan
+checks for a given port and send the traffic though the same conntrack zone.
+In order to better visualize this and reduce the amount of duplicated flows
+that are ineviably printed in this view, these flows are combined into a block
+and the match keys that are equal for all flows are removed.
+
+Here is an example.
+::
+
+  Datapath Flows (logical)
+  └── ╭────────────────────────────────╮
+      │ [recirc_id(0x0) in_port(eth0)] │
+      ╰────────────────────────────────╯
+      └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+          │ recirc_id(0),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0/0),ct_zone(0/0),ct_mark(0/0),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ipv4(src=10.132.0.7,dst=1 │
+          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                                                   │
+          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:01),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
+          │ 0.0.0.0/255.255.128.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=32768/0x8000,dst=0/0), packets:711, bytes:114236,                                                                                                         │
+          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
+          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660,                                                                                                          │
+          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:22),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
+          │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:1, bytes:66,                                                                                                          │
+          │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:09),eth_type(......),ipv4(src=10.132.0.7,dst=1 │
+          │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:0, bytes:0,                                                                                                                 │
+          ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+          └── ╭───────────────────────────────────────╮
+              │ actions: ct(zone=32,nat),recirc(0xc1) │
+              ╰───────────────────────────────────────╯
+              └── ╭─────────────────────────────────╮
+                  │ [recirc_id(0xc1) in_port(eth0)] │
+                  ╰─────────────────────────────────╯
+                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ip │
+                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961,                                                                  │
+                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+                  │   └── ╭───────────────────────────────────────╮
+                  │       │ actions: ct(zone=14,nat),recirc(0xc2) │
+                  │       ╰───────────────────────────────────────╯
+                  │       └── ╭─────────────────────────────────╮
+                  │           │ [recirc_id(0xc2) in_port(eth0)] │
+                  │           ╰─────────────────────────────────╯
+                  │           └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+                  │               │ recirc_id(0xc2),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0x1),ct_label(0/0),eth(src=00:00:00:00:00:00/00:00:00:00:00:00,dst=00:00:00 │
+                  │               │ :00:00:00/01:00:00:00:00:00),eth_type(0x0800),ipv4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=0/0,tos=0/0,ttl=0/0,frag=no), packets:4924, bytes:468961,                                        │
+                  │               ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+                  │               └── ╭──────────────────────╮
+                  │                   │ actions: ovn-k8s-mp0 │
+                  │                   ╰──────────────────────╯
+                  ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
+                  │   │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(0x0800),ip │
+                  │   │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660                                                                          │
+                  │   ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
+
+
+The above shows a part of a bigger tree where a first block of flows
+at ``recirc_id(0)`` that match on different destination ethernet
+addresses and protocols and send traffic through conntrack (zone 32).
+
+Then some additional flows at ``recirc_id(0xc1)`` process each traffic
+connection independently. One of them, shown in the example, sends the traffic
+through conntrack zone 14 and after another recirculation the packet is
+ultimately sent through a port.
+
+This is a truly complex multi-zone conntrack pipeline that is now fairly
+clear thanks to this visualization.
+
+Also note, the flows in the block are conveniently sorted by sent packets.
+
+This example shows only a single "subtree". Even though the combination of
+flows with the same action helps, if we use this command to display a big
+dump, the output can be lengthy. Here are two (combinable) ways to
+help out.
+
+
+Plotting datapath trees
+~~~~~~~~~~~~~~~~~~~~~~~
+
+By using the ``ovs-flowviz datapath html`` format, long datapath trees can
+be displayed in an interactive HTML table. The resulting web allows you to
+collapse and expand subtrees so you can focus on what you're looking for.
+
+In addition, the ``ovs-flowviz datapath graph`` format generates a graphviz
+graph definition where each block of flows with the same ``recirc_id`` match
+are arranged together and edges are created to represent recirculations.
+Also, this format comes with further goodies such as displaying the conntrack
+zones which are key to understanding what the datapath is really doing with a
+packet.
+
+These two formats (``html`` and ``graph``) can even be combined. By using the
+``ovs-flowviz datapath graph --html`` command, you'll get an interactive
+HTML table alongside a `svg` graphical representation of the flows. Click on
+a flow on the svg and it'll take you to the corresponding entry in the
+flow table.
+
+
+Filtering
+~~~~~~~~~
+
+Apart from being able to expand and collapse subtrees, we can use filtering.
+
+However, filtering works in a slightly different way compared with OpenFlow
+flows. Instead of just removing non-matching flows, the output
+of a filtered datapath flow tree will show full sub-trees that contain at
+least one flow that satisfies the filter.
+
+For example, let's take the flows in the above example, and let's imagine we
+want to understand what traffic is going out on port ``ovn-k8s-mp0``. We
+could run the tool as:
+::
+
+   $ ovs-appctl dpctl/dump-flows | ovs-flowviz -f "output.port=ovn-k8s-mp0" datapath tree
+
+The resulting flow tree will contain all of the flows above, even those
+with ``recirc_id(0)`` and ``recirc_id(0xc1)`` that don't actually output
+traffic to port ``ovn-k8s-mp0``. Why? because they are all part of a subtree
+that contains flows that do output packets on port ``ovn-k8s-mp0``
+
+That way, we see the "full picture" of how traffic on ending up in a particular
+port is being processed.
+
+.. _ovs-flowviz(8): https://docs.openvswitch.org/en/latest/ref/ovs-flowviz.8
diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst
index f239fcf83..9ddb145dd 100644
--- a/Documentation/topics/index.rst
+++ b/Documentation/topics/index.rst
@@ -58,3 +58,4 @@  OVS
    userspace-checksum-offloading
    userspace-tx-steering
    usdt-probes
+   flow-visualization
diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
index 94b6d7431..d228ef1d3 100644
--- a/rhel/openvswitch-fedora.spec.in
+++ b/rhel/openvswitch-fedora.spec.in
@@ -499,6 +499,7 @@  fi
 %{_mandir}/man8/ovs-ctl.8*
 %{_mandir}/man8/ovs-dpctl.8*
 %{_mandir}/man8/ovs-dpctl-top.8*
+%{_mandir}/man8/ovs-flowviz.8*
 %{_mandir}/man8/ovs-kmod-ctl.8*
 %{_mandir}/man8/ovs-ofctl.8*
 %{_mandir}/man8/ovs-pki.8*
diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
index 9903dd10a..437e01ee1 100644
--- a/rhel/openvswitch.spec.in
+++ b/rhel/openvswitch.spec.in
@@ -223,6 +223,7 @@  exit 0
 /usr/share/man/man8/ovs-ctl.8.gz
 /usr/share/man/man8/ovs-dpctl.8.gz
 /usr/share/man/man8/ovs-dpctl-top.8.gz
+/usr/share/man/man8/ovs-flowviz.8.gz
 /usr/share/man/man8/ovs-kmod-ctl.8.gz
 /usr/share/man/man8/ovs-ofctl.8.gz
 /usr/share/man/man8/ovs-parse-backtrace.8.gz