Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

L4S simulation is simplistic #1825

Open
huitema opened this issue Jan 25, 2025 · 1 comment
Open

L4S simulation is simplistic #1825

huitema opened this issue Jan 25, 2025 · 1 comment

Comments

@huitema
Copy link
Collaborator

huitema commented Jan 25, 2025

The test code has minimal support for L4S-style active queue management: each link description has an l4smax property. If the queue size exceeds that value, the incoming packets are marked as congestion experienced. This allows us to test the Prague or ECN code paths, but it is not quite the "Dual-Queue Coupled AQM" described in RFC 9332:

  • There should be two queues, the L4S queue for packets with ECT(1) or CE marks, the default queue for other packets.
  • The algorithm maintains a "marking rate" to be applied to L4S packets.
    • The marking rate is driven by a proportional controller, increasing when the L4S queuing time exceeds the target, decreasing when it is lower.
    • L4S packets are marked according to the marking rate
  • The algorithm derives an "early drop rate" for the default queue, computed as the square of the marking rate.
    • According to specification, packets with ECT(0) are marked with the early drop rate. Packets without ECN marks are dropped at that rate.
  • If there is no L4S traffic, the marking rate returns to zero
  • If the queue is completely full, packets are dropped.

The multiple queue requirement is hard to satisfy in our architecture. The current design has just one queue, serviced first-in first-out. If we introduce two queues, we have to decide a service rate for each of the queues. The L4S specification envisages giving priority to the L4S queue, but that does not seem fair if there is just one connection using the L4S queue and many using the default queue.

The handling can be simplified by using just one queue, but computing a marking rate based on the delay experienced by L4S packets. We could update the marking rate for each incoming L4S packet. This is wrong because if there is no incoming L4S packet at all, the marking rate remains constant. We could update the marking rate periodically, based on the longest delay experienced by L4S packet during that period -- or zero if there are no L4S packets. We could also update the marking rate periodically, but using delay measurement for every incoming packet, L4S or not, which is more robust -- and is indeed what appears recommended for the PI2. It does have the downside of preventing non L4S traffic from building queues, which might slow it somewhat -- but that's logical if we only manage a single queue.

When doing periodic updates, we have to consider the "response time" of the system. If a router increases the marking rate, the traffic will only slow down after about 1 RTT -- but the router does not know the end-to-end RTT. Applying control actions too often or too brutally can lead to oscillations. The original PI controller uses a default period of 15ms, which is the same as the target latency value. In our simulation, the target latency value is the parameter l4smax -- we may want to use it for both the update period and the latency target.

@huitema
Copy link
Collaborator Author

huitema commented Jan 26, 2025

The PI2 algorithm is specified in Annex A of RFC 9332.

The initial variables are:

1:  dualpi2_params_init(...) {         % Set input parameter defaults
2:    % DualQ Coupled framework parameters
5:    limit = MAX_LINK_RATE * 250 ms               % Dual buffer size
3:    k = 2                                         % Coupling factor
4:    % NOT SHOWN % scheduler-dependent weight or equival't parameter
6:
7:    % PI2 Classic AQM parameters
8:    target = 15 ms                             % Queue delay target
9:    RTT_max = 100 ms                      % Worst case RTT expected
10:   % PI2 constants derived from above PI2 parameters
11:   p_Cmax = min(1/k^2, 1)             % Max Classic drop/mark prob
12:   Tupdate = min(target, RTT_max/3)        % PI sampling interval
13:   alpha = 0.1 * Tupdate / RTT_max^2      % PI integral gain in Hz
14:   beta = 0.3 / RTT_max               % PI proportional gain in Hz
15:
16:   % L4S ramp AQM parameters
17:   minTh = 800 us        % L4S min marking threshold in time units
18:   range = 400 us                % Range of L4S ramp in time units
19:   Th_len = 1 pkt           % Min L4S marking threshold in packets
20:   % L4S constants
21:   p_Lmax = 1                               % Max L4S marking prob
22: }

Note that alpha and beta are expressed in Hertz, which assumes times measured in seconds. Picoquic measures times in microseconds, so we will have to adapt the computation, or use floating points and convert all times to seconds.

The packet handling and rate update code utilizes the objects lq (L4S queue) and cq (Classic queue), with properties:

  • cq.time(): current queuing time for classic packet

The update algorithms runs every Tupdate interval, and uses the variables curq (current queuing time) and prevq (previous queuing time).

1:  dualpi2_update(lq, cq) {                % Update p' every Tupdate
2:    curq = cq.time()  % use queuing time of first-in Classic packet
3:    p' = p' + alpha * (curq - target) + beta * (curq - prevq)
4:    p_CL = k * p'  % Coupled L4S prob = base prob * coupling factor
5:    p_C = p'^2                       % Classic prob = (base prob)^2
6:    prevq = curq
7:  }

The proposed implementation distinguishes time to "enqueue" and "dequeue". We don't really do that in our simulation architecture, since we combine the two functions and mark the packet with a dequeue time corresponding to delivery over the link, but the key parts are:

  • if the queue is full, the packet is dropped,
  • else we run a random process, such that:
    - pick a random number y
    - if this is an L4S packet, mark as CE if y < p_CL
    - else if this is a classic packet with ECT(0), mark as CE if y < p_C
    - else drop if y < p_C

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant