Supporting a new queuing discipline

The following example illustrates how to add support for the pfifo queuing discipline:

from linuxnet.qos.extension import (QDisc, unitstr2int,
                                    QDiscParser, QDiscOutput)

class PFifoQDisc(QDisc):
    """The new Python class must be a child of QDisc
    """
    def __init__(self, handle, parent_handle, packet_limit=None):
        super().__init__(handle, parent_handle)
        self.__packet_limit = packet_limit

    def qdisc_creation_args(self):
        """This method is required; it should return the queuing
        discipline-specific tc(8) arguments for creating this
        queuing discipline
        """
        args = ['pfifo']
        if self.__packet_limit is not None:
            args.extend(['limit', str(self.__packet_limit)])
        return args

    def get_description(self):
        """This method is optional but recommended; it returns a
        description of the particular queuing discipline instantation
        """
        retval = super().get_description()
        if self.__packet_limit is not None:
            retval += f' limit {self.__packet_limit} packets'
        return retval

    def get_packet_limit(self):
        """This is a pfifo-specific method
        """
        return self.__packet_limit

    @classmethod
    def parse(cls, qdisc_output: QDiscOutput):
        """This method is required. It creates a PFifoQDisc instance
        from the output of tc(8).
        """
        field_iter = qdisc_output.get_field_iter()
        packet_limit = None
        for field in field_iter:
            if field == 'limit':
                limitstr = next(field_iter)
                # limit value should have 'p' suffix
                packet_limit = unitstr2int(limitstr, 'p')
        return PFifoQDisc(qdisc_output.get_handle(),
                                qdisc_output.get_parent_handle(),
                                packet_limit)

#
# Register the new queueing discipline with the parser.
#
QDiscParser.register_qdisc('pfifo', PFifoQDisc)

The steps to add support for a new queuing discipline are:

  1. Creation of a new Python class that inherits from QDisc; the convention is to name such a class xxxQDisc where xxx is the name of the queuing discipline

  2. The xxxQDisc class must implement the qdisc_creation_args() method; this method should return the queuing discipline-specific tc(8) arguments for creating the queuing discipline

  3. The xxxQDisc class must implement the class method parse() to create an xxxQDisc instance from a line of tc(8) output encapsulated in a QDiscOutput instance

  4. The xxxQDisc class must be registered by invoking the QDiscParser.register_qdisc() method.

  5. The xxxQDisc class should implement the method get_description()

  6. The __init__() method of xxxQDisc should take as arguments the particular parameters of the new queuing discipline

  7. The xxxQDisc class should provide getter methods for the particular parameters of the queuing discipline

The argument to the xxxQDisc.parse() method is a QDiscOutput instance which contains the unparsed portion of the qdisc line and zero or more lines holding qdisc statistics. For example:

qdisc sfq 103: parent 1:103 limit 127p quantum 1500b perturb 10sec

The line has already already been parsed up to and including the parent 1:103 field; the rest of the line is specific to the queuing discipline. The xxxQDisc.parse() method is responsible for parsing those fields starting with limit. An iterator over those fields can be obtained by invoking the QDiscOutput.get_field_iter() method.

Supporting a classful queuing discipline

If the queuing discipline is classful then in addition to the creation of a Python class for the queuing discipline, a new Python class will also be needed to represent the queuing class. The steps are as follows:

  1. Creation of a new Python class that inherits from QClass; the convention is to name such a class xxxQClass where xxx is the name of the queuing discipline

  2. The xxxQClass class must implement the method qclass_creation_args(); this method should return the queuing class-specific tc(8) arguments for creating a class of the queuing discipline (example: HTBQClass.qclass_creation_args())

  3. The xxxQDisc class must implement the class method parse() to create an xxxQClass instance from a line of tc(8) output encapsulated in a QClassOutput instance

  4. The xxxQClass class must be registered by invoking the QClassParser.register_qclass() method.

  5. The xxxQClass class should implement the method get_description() (example: HTBQClass.get_description())

  6. The __init__() method of xxxQClass should take as arguments the particular parameters of the queuing class

  7. The xxxQClass class should provide getter methods for the particular parameters of the queuing class

The argument to the xxxQDisc.parse() method is a QClassOutput instance which contains the unparsed portion of the class line and zero or more lines holding class statistics. For example:

class htb 1:1 root rate 2000Kbit ceil 2000Kbit burst 5000b cburst 5000b

The line has already already been parsed up to and including the root field; the rest of the line is specific to the queuing class. The xxxQClass.parse() method is responsible for parsing those fields starting with rate. An iterator over those fields can be obtained by invoking the QClassOutput.get_field_iter() method.

If the queuing discipline is classful and its classes are generated automatically upon instantiation in the kernel (e.g. the PRIO queuing discipline), the QDisc._instantiate_qdisc() method must be overriden to create the necessary subclasses of QClass upon successful instantiation of the queuing discipline (see PrioQDisc for an example).

Queuing statistics

If the new queuing discipline (or class) reports additional statistics, then a new class should be created to hold them. The steps needed are as follows:

  1. Creation of a new Python class that inherits from QStats; the convention is to name such a class xxxQDiscStats (for queuing discipline statistics) or xxxQClassStats (for queuing class statistics) where xxx is the name of the queuing discipline

  2. the xxxQDisc (or xxxQClass) class must implement the get_stats() method; this method should return an instance of xxxQDiscStats (or xxxQClassStats)

  3. the xxxQDisc (or xxxQClass) class must implement the _parse_stats() method; this method should create a xxxQDiscStats (or xxxQClassStats) instance which should be stored in the xxxQDisc (or xxxQClass) instance. It should then invoke the init_from_output() method of the new xxxQDiscStats (or xxxQClassStats) instance

    The argument to the _parse_stats() method is a LineGroupIterator which returns the output lines that contain the statistics. This iterator is passed to the invoked init_from_output() method.

  4. the xxxQDiscStats (or xxxQClassStats) class must implement the init_from_output() method; this method should first invoke the QStats.init_from_output() method which will parse the output lines that contain the common queuing discipline statistics, and then it should proceed to parse the statistics lines that are specific to the new queuing discipline/class

    The argument to this method is a LineGroupIterator instance which provides access to the statistics lines.


Python Classes

QDisc

class QDisc(handle: Handle, parent_handle: Optional[Handle])[source]

Bases: QNode

Base class for all queueing discipline classes

Parameters:
  • handleHandle of this QDisc

  • parent_handleHandle of the parent of this QDisc (if None, this is a root QDisc)

_instantiate_qdisc(config) None[source]

Invoke the tc(8) command to create the queuing discipline described by this object.

Parameters:

config – a QDiscConfig object

dump(outfile: TextIO, level=0, qclass_map: Optional[Mapping[Handle, QClass]] = None)[source]

Recursively dump this Qdisc to outfile.

The qclass_map, if present, is used to determine the destination QClass objects of traffic filters.

get_description() str[source]

Return a string that fully describes this QDisc

is_default() bool[source]

Returns True if this QDisc is the default qdisc used by the kernel.

static is_ingress() bool[source]

Returns True if this QDisc is the ingress qdisc

is_root() bool[source]

Returns True if this is a root QDisc

qdisc_creation_args() List[str][source]

Returns the qdisc-specific arguments passed to tc(8) to create the particular qdisc

It must be implemented by the derived Python class.


QClass

class QClass(handle: Handle, parent_handle: Handle, class_name: Optional[str] = None)[source]

Bases: QNode

Used as a base class for all queuing discipline specific classes

A QClass object can either have a queuing discipline as a child, or it can have a set of QClass objects.

Parameters:
  • handleHandle of this QClass

  • parent_handleHandle of the parent of this QClass

  • class_name – optional class name; defaults to the class handle

dump(outfile: TextIO, level: int, qclass_map: Optional[Mapping[Handle, QClass]] = None)[source]

Recursively dump this QClass to outfile.

The qclass_map, if present, is used to determine the destination QClass objects of traffic filters.

get_class_name() str[source]

Returns the class name

get_description() str[source]

Return a string that fully describes this QCclass

get_qdisc() Optional[QDisc][source]

Returns the QDisc under with this QClass, or None if this is not a leaf queuing class.

is_leaf() bool[source]

Returns True if this is a leaf queuing class

qclass_creation_args() List[str][source]

Returns the class-specific arguments passed to tc to create the particular class

It must be implemented by the derived Python class.

set_class_name(class_name: str) None[source]

Sets the class name

set_qdisc(qdisc: QDisc) None[source]

Set the QDisc under with this queuing class


QNode

class QNode(handle: Handle, parent_handle: Optional[Handle])[source]

Used as a base class for the Python classes QDisc and QClass which are the actual nodes of the traffic classification tree.

A path from the root of the tree down to a leaf looks like this:

QDisc -> QClass+ -> QDisc

where the QClass+ -> QDisc can repeat 0 or more times, and the final QDisc may not be present (pfifo is implied)

Parameters:
create_filter(traffic_filter: TrafficFilter) None[source]

Create the filter traffic_filter.

delete_filter(traffic_filter: TrafficFilter) None[source]

Delete the filter traffic_filter.

dump(outfile: TextIO, level: int, qclass_map: Mapping[Handle, QClass]) None[source]

Recursively dump this node and all its child classes to outfile

get_child(handle: Handle) Optional[QClass][source]

Returns the QClass with the specified handle

get_child_count() int[source]

Return number of children of this QDisc/QClass

get_child_iter() Iterator[QClass][source]

Returns an iterator for the QClass children of this QDisc/QClass.

get_children() List[QClass][source]

Returns the children of this QDisc/QClass.

get_config() QDiscConfig[source]

Returns the QDiscConfig where this QDisc/QClass has been instantiated, or None if not instantiated.

get_description() str[source]

Return a string that fully describes this node (name + attributes)

get_filters(refresh=False) List[TrafficFilter][source]

Returns (a copy of) the list of filters at this QDisc/QClass

Parameters:

refresh – if True, a fresh copy of the filter list is obtained using the tc(8) command.

get_handle() Handle[source]

Returns the QDisc/QClass handle

get_interface() Optional[str][source]

Returns the interface where this QDisc/QClass has been instantiated, or None if the QDisc/QClass is not instantiated.

get_parent_handle() Optional[Handle][source]

Returns the handle of the parent of this QDisc/QClass

get_stats() Optional[QStats][source]

Returns queuing stats.