lsquic_bw_sampler.c revision 7d09751d
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc.  See LICENSE. */
2#include <assert.h>
3#include <inttypes.h>
4#include <stddef.h>
5#include <stdint.h>
6#include <sys/queue.h>
7
8#include "lsquic_int_types.h"
9#include "lsquic_types.h"
10#include "lsquic_hash.h"
11#include "lsquic.h"
12#include "lsquic_conn.h"
13#include "lsquic_malo.h"
14#include "lsquic_util.h"
15#include "lsquic_packet_common.h"
16#include "lsquic_packet_out.h"
17#include "lsquic_parse.h"
18#include "lsquic_bw_sampler.h"
19
20#define LSQUIC_LOGGER_MODULE LSQLM_BW_SAMPLER
21#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(sampler->bws_conn)
22#include "lsquic_logger.h"
23
24
25int
26lsquic_bw_sampler_init (struct bw_sampler *sampler, struct lsquic_conn *conn,
27                                                enum quic_ft_bit retx_frames)
28{
29    struct malo *malo;
30
31    assert(lsquic_is_zero(sampler, sizeof(*sampler)));
32
33    malo = lsquic_malo_create(sizeof(struct bwp_state));
34    if (!malo)
35        return -1;
36
37    sampler->bws_malo = malo;
38    sampler->bws_conn = conn;
39    sampler->bws_retx_frames = retx_frames;
40    LSQ_DEBUG("init");
41    return 0;
42}
43
44
45void
46lsquic_bw_sampler_app_limited (struct bw_sampler *sampler)
47{
48    sampler->bws_flags |= BWS_APP_LIMITED;
49    sampler->bws_end_of_app_limited_phase = sampler->bws_last_sent_packno;
50    LSQ_DEBUG("app limited, end of limited phase is %"PRIu64,
51                                    sampler->bws_end_of_app_limited_phase);
52}
53
54
55void
56lsquic_bw_sampler_cleanup (struct bw_sampler *sampler)
57{
58    if (sampler->bws_conn)
59        LSQ_DEBUG("cleanup");
60    if (sampler->bws_malo)
61        lsquic_malo_destroy(sampler->bws_malo);
62}
63
64
65/* This module only fails when it is unable to allocate memory.  This rarely
66 * happens, so we avoid having to check return values and abort the connection
67 * instead.
68 */
69static void
70bw_sampler_abort_conn (struct bw_sampler *sampler)
71{
72    if (!(sampler->bws_flags & BWS_CONN_ABORTED))
73    {
74        sampler->bws_flags |= BWS_CONN_ABORTED;
75        LSQ_WARN("aborting connection");
76        sampler->bws_conn->cn_if->ci_internal_error(sampler->bws_conn,
77                                                    "resources exhausted");
78    }
79}
80
81
82#define BW_WARN_ONCE(msg...) do {                                           \
83    if (!(sampler->bws_flags & BWS_WARNED))                                 \
84    {                                                                       \
85        sampler->bws_flags |= BWS_WARNED;                                   \
86        LSQ_WARN(msg);                                                      \
87    }                                                                       \
88} while (0)
89
90void
91lsquic_bw_sampler_packet_sent (struct bw_sampler *sampler,
92                    struct lsquic_packet_out *packet_out, uint64_t in_flight)
93{
94    struct bwp_state *state;
95    unsigned short sent_sz;
96
97    if (packet_out->po_bwp_state)
98    {
99        BW_WARN_ONCE("sent: packet %"PRIu64" already has state",
100                                                        packet_out->po_packno);
101        return;
102    }
103
104    sampler->bws_last_sent_packno = packet_out->po_packno;
105
106    if (!(packet_out->po_frame_types & sampler->bws_retx_frames))
107        return;
108
109    sent_sz = lsquic_packet_out_sent_sz(sampler->bws_conn, packet_out);
110    sampler->bws_total_sent += sent_sz;
111
112    // If there are no packets in flight, the time at which the new transmission
113    // opens can be treated as the A_0 point for the purpose of bandwidth
114    // sampling. This underestimates bandwidth to some extent, and produces some
115    // artificially low samples for most packets in flight, but it provides with
116    // samples at important points where we would not have them otherwise, most
117    // importantly at the beginning of the connection.
118    if (in_flight == 0)
119    {
120        sampler->bws_last_acked_packet_time = packet_out->po_sent;
121        sampler->bws_last_acked_total_sent = sampler->bws_total_sent;
122        // In this situation ack compression is not a concern, set send rate to
123        // effectively infinite.
124        sampler->bws_last_acked_sent_time = packet_out->po_sent;
125    }
126
127    state = lsquic_malo_get(sampler->bws_malo);
128    if (!state)
129    {
130        bw_sampler_abort_conn(sampler);
131        return;
132    }
133
134    state->bwps_send_state = (struct bwps_send_state) {
135        .total_bytes_sent   = sampler->bws_total_sent,
136        .total_bytes_acked  = sampler->bws_total_acked,
137        .total_bytes_lost   = sampler->bws_total_lost,
138        .is_app_limited     = !!(sampler->bws_flags & BWS_APP_LIMITED),
139    };
140    state->bwps_sent_at_last_ack = sampler->bws_last_acked_total_sent;
141    state->bwps_last_ack_sent_time = sampler->bws_last_acked_sent_time;
142    state->bwps_last_ack_ack_time = sampler->bws_last_acked_packet_time;
143    state->bwps_packet_size = sent_sz;
144
145    packet_out->po_bwp_state = state;
146
147    LSQ_DEBUG("add info for packet %"PRIu64, packet_out->po_packno);
148}
149
150
151void
152lsquic_bw_sampler_packet_lost (struct bw_sampler *sampler,
153                                    struct lsquic_packet_out *packet_out)
154{
155    if (!packet_out->po_bwp_state)
156        return;
157
158    sampler->bws_total_lost += packet_out->po_bwp_state->bwps_packet_size;
159    lsquic_malo_put(packet_out->po_bwp_state);
160    packet_out->po_bwp_state = NULL;
161    LSQ_DEBUG("packet %"PRIu64" lost, total_lost goes to %"PRIu64,
162                            packet_out->po_packno, sampler->bws_total_lost);
163}
164
165
166struct bw_sample *
167lsquic_bw_sampler_packet_acked (struct bw_sampler *sampler,
168                struct lsquic_packet_out *packet_out, lsquic_time_t ack_time)
169{
170    const struct bwp_state *state;
171    struct bw_sample *sample;
172    struct bandwidth send_rate, ack_rate;
173    lsquic_time_t rtt;
174    unsigned short sent_sz;
175    int is_app_limited;
176
177    if (!packet_out->po_bwp_state)
178        return 0;
179
180    state = packet_out->po_bwp_state;
181    sent_sz = lsquic_packet_out_sent_sz(sampler->bws_conn, packet_out);
182
183    sampler->bws_total_acked += sent_sz;
184    sampler->bws_last_acked_total_sent = state->bwps_send_state.total_bytes_sent;
185    sampler->bws_last_acked_sent_time = packet_out->po_sent;
186    sampler->bws_last_acked_packet_time = ack_time;
187
188    // Exit app-limited phase once a packet that was sent while the connection
189    // is not app-limited is acknowledged.
190    if ((sampler->bws_flags & BWS_APP_LIMITED)
191            && packet_out->po_packno > sampler->bws_end_of_app_limited_phase)
192    {
193        sampler->bws_flags &= ~BWS_APP_LIMITED;
194        LSQ_DEBUG("exit app-limited phase due to packet %"PRIu64" being acked",
195                                                        packet_out->po_packno);
196    }
197
198    // There might have been no packets acknowledged at the moment when the
199    // current packet was sent. In that case, there is no bandwidth sample to
200    // make.
201    if (state->bwps_last_ack_sent_time == 0)
202        goto no_sample;
203
204    // Infinite rate indicates that the sampler is supposed to discard the
205    // current send rate sample and use only the ack rate.
206    if (packet_out->po_sent > state->bwps_last_ack_sent_time)
207        send_rate = BW_FROM_BYTES_AND_DELTA(
208            state->bwps_send_state.total_bytes_sent
209                                    - state->bwps_sent_at_last_ack,
210            packet_out->po_sent - state->bwps_last_ack_sent_time);
211    else
212        send_rate = BW_INFINITE();
213
214    // During the slope calculation, ensure that ack time of the current packet is
215    // always larger than the time of the previous packet, otherwise division by
216    // zero or integer underflow can occur.
217    if (ack_time <= state->bwps_last_ack_ack_time)
218    {
219        BW_WARN_ONCE("Time of the previously acked packet (%"PRIu64") is "
220            "is larger than the ack time of the current packet (%"PRIu64")",
221            state->bwps_last_ack_ack_time, ack_time);
222        goto no_sample;
223    }
224
225    ack_rate = BW_FROM_BYTES_AND_DELTA(
226        sampler->bws_total_acked - state->bwps_send_state.total_bytes_acked,
227        ack_time - state->bwps_last_ack_ack_time);
228    LSQ_DEBUG("send rate: %"PRIu64"; ack rate: %"PRIu64, send_rate.value,
229                                                            ack_rate.value);
230
231    // Note: this sample does not account for delayed acknowledgement time.
232    // This means that the RTT measurements here can be artificially high,
233    // especially on low bandwidth connections.
234    rtt = ack_time - packet_out->po_sent;
235    is_app_limited = state->bwps_send_state.is_app_limited;
236
237    /* After this point, we switch `sample' to point to `state' and don't
238     * reference `state' anymore.
239     */
240    sample = (void *) packet_out->po_bwp_state;
241    packet_out->po_bwp_state = NULL;
242    if (BW_VALUE(&send_rate) < BW_VALUE(&ack_rate))
243        sample->bandwidth = send_rate;
244    else
245        sample->bandwidth = ack_rate;
246    sample->rtt = rtt;
247    sample->is_app_limited = is_app_limited;
248
249    LSQ_DEBUG("packet %"PRIu64" acked, bandwidth: %"PRIu64" bps",
250                        packet_out->po_packno, BW_VALUE(&sample->bandwidth));
251
252    return sample;
253
254  no_sample:
255    lsquic_malo_put(packet_out->po_bwp_state);
256    packet_out->po_bwp_state = NULL;
257    return NULL;;
258}
259
260
261unsigned
262lsquic_bw_sampler_entry_count (const struct bw_sampler *sampler)
263{
264    void *el;
265    unsigned count;
266
267    count = 0;
268    for (el = lsquic_malo_first(sampler->bws_malo); el;
269                                    el = lsquic_malo_next(sampler->bws_malo))
270        ++count;
271
272    return count;
273}
274