精华内容
下载资源
问答
  • Pacing

    2014-11-23 17:27:41
    Iteration,迭代。通过设置,可以指定虚拟用户在同一个Action中重复执行多次,每次重复称之为一个iteration。Iteration可以帮助我们模拟...从定义上来看,Pacing是和iteration绑定在一起的,可以认为是iteration pacing

    With是指前一次Ending Iteration到下一次Starting Iteration之间的时间。
    At是指前一次Starting Iteration到下一次Starting Iteration之间的时间,所以At包含了前一次迭代执行的时间。
    在At模式下,一次迭代执行的时间若大于Pacing的时间,则系统会提示无法达到Pacing的时间设置,脚本不做等待立即运行下一次迭代,可以用来验证下一次迭代的处理时间是否在期望的阀值内。


    Iteration,迭代。通过设置,可以指定虚拟用户在同一个Action中重复执行多次,每次重复称之为一个iteration。Iteration可以帮助我们模拟现实世界的重复场景。


    Pacing,步调。可以通过设置两次迭代之间的间隔时间,来调整各个action之间的步调(或者称之为节奏)。从定义上来看,Pacing是和iteration绑定在一起的,可以认为是iteration pacing。

    Think time,思考时间。可以通过设置思考时间,来模拟真实用户在操作过程中的等待时间。从定义上来看,think time是在iteration内部的某个action中各个步骤的间隔时间。
    _______________________________________________________________________________________________________________________________________________________
    淘宝购物车应用,假定每分钟有6000个在线用户浏览(Browser),共10台服务器。用户在浏览过程中,先打开购物车页面,花了1秒钟时间浏览整个页面,然后查看其中的某个宝贝。此时,在性能测试场景中(如果模拟真实场景的话),就会在两个步骤之间设置一个等待时间think time = 1 秒。
     
    假设用户进行一次上述操作会消耗5秒钟的时间,即完成整个迭代需要5秒钟。如果用户不停顿,继续第二次重复操作,则同样耗费约5秒左右的时间。但是真实世界中肯定是有停顿的。一个真正的用户,做完一系列操作后,会间隔一段时间。假定用户停顿了5秒,再第二次重复操作,则一共耗费10秒钟时间。映射到 loadrunner中,就需要在一次iteration中,设置一个think time = 1秒,然后在两个iteration之间,设置一个pacing为5秒。
     
      一个虚拟用户在1分钟内,就能完成6次的迭代,12次请求。而要达到有6000个用户的浏览量,我们需要6000/12 /10= 50个虚拟用户。
      通过上面的例子,相信大家对pacing和think time已经有了相应的理解。至于在loadrunner中符合使用、怎样配置,则可以通过帮助文档来加深认识。在此不一一说明。
      尽管性能测试的需求通常都是从客户端角度来定义的,比如“系统需要支撑100个并发用户”、“每分钟有6000个请求量”,但是压力应该以服务器为中心来看待。原因很简单,从loadrunenr端发送出去的请求,需要等待服务器端响应之后才能发送下一个请求。可以说,对于每个虚拟用户,它对服务器发送请求的频率取决于服务器对请求的处理时间。
      如果需要模拟真实用户的操作,从而模拟服务器端的真实变动,think time和pacing是两个必不可少的设置项。

          从以上使用场景来看,pacing和think time都是可以模仿真实世界中的停顿。这是从客户端考虑的。
      从服务器端考虑,这两个参数的设置将会更加复杂。
      例如,我们需要衡量服务器处理一个请求的平均响应时间。考虑,服务器端能同时并发处理的请求数一定,当性能测试发送的每秒请求数超过它能处理的请求数后,再到达的请求将会在服务器系统中排队等待,这时,整个响应时间的计时已经开始,排队等待时间将会计入响应时间。所以,如果LR仍然持续发送请求,可能造成接下来的请求都在等待。这时,服务器每次处理的事务数在下降,平均响应时间在增大,造成了请求发送越快,处理越少越慢。
      对于这种情况,从服务器端出发,来考虑设置请求发送的速度。”
      悟石:“从服务器端来看,让每颗CPU都忙碌起来,是件好事。当压力超过CPU能承受范围时,认为是过载,等待队列会越来越长,load不断飙升。
      但如果真实情况是存在瞬间超高压,中间会有停顿的话,服务器或许能撑得过去(风险很大)。对于复杂场景,这个停顿要靠pacing来完成。不过,pacing怎么设置才最合适,是需要研究用户行为才能定的。”

    展开全文
  • Implement pacing

    2020-12-06 14:38:43
    <div><p>As <a href="https://quicwg.org/base-drafts/draft-ietf-quic-recovery.html#pacing">discussed in the recovery document</a>.</p><p>该提问来源于开源项目:quinn-rs/quinn</p></div>
  • TCP Pacing功能

    千次阅读 2019-04-18 22:42:17
    TCP Pacing功能控制TCP的发包速率。 Pacing的初始化 TCP协议初始函数tcp_sk_init中,赋值了两个Pacing相关的参数,分别为sysctl_tcp_pacing_ss_ratio和sysctl_tcp_pacing_ca_ratio,都是控制pacing速率的倍数值...

     

    TCP Pacing功能控制TCP的发包速率。


    Pacing的初始化


    TCP协议初始函数tcp_sk_init中,赋值了两个Pacing相关的参数,分别为sysctl_tcp_pacing_ss_ratio和sysctl_tcp_pacing_ca_ratio,都是控制pacing速率的倍数值。前者应用中慢启动阶段,默认值为200,即将速率提升200%;后者应用在拥塞避免阶段,默认值为120,即将速率提升120%。

    static int __net_init tcp_sk_init(struct net *net)
    {
        net->ipv4.sysctl_tcp_pacing_ss_ratio = 200;
        net->ipv4.sysctl_tcp_pacing_ca_ratio = 120;
    }
     
    $ cat /proc/sys/net/ipv4/tcp_pacing_ss_ratio
    200
    $ cat /proc/sys/net/ipv4/tcp_pacing_ca_ratio
    120

    另外,在TCP定时器初始化函数tcp_init_xmit_timers中,内核初始化一个高精度的pacing定时器,超时处理函数设定为tcp_pace_kick。

    void tcp_init_xmit_timers(struct sock *sk)
    {
        inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer, &tcp_keepalive_timer);
        hrtimer_init(&tcp_sk(sk)->pacing_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED);
        tcp_sk(sk)->pacing_timer.function = tcp_pace_kick;
    }

    最后在套接口初始化函数sock_init_data中,初始化三个pacing相关的参数。其中最大的速率sk_max_pacing_rate和当前速率sk_pacing_rate设置为最大的无符号整型值,而sk_pacing_shift设置为10。

    void sock_init_data(struct socket *sock, struct sock *sk)
    {
        sk->sk_max_pacing_rate = ~0U;
        sk->sk_pacing_rate = ~0U;
        sk->sk_pacing_shift = 10;
    }

    Pacing功能开启

    TCP拥塞控制算法BBR需要pacing功能的支持,在其初始化函数bbr_init中,将pacing状态sk_pacing_status设置为SK_PACING_NEEDED使能pacing功能。另外,用户可通过setsockopt系统调用的选项SO_MAX_PACING_RATE设置pacing的最大速率,其隐含的打开套接口的pacing功能。

    static void bbr_init(struct sock *sk)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct bbr *bbr = inet_csk_ca(sk);
    
        bbr->has_seen_rtt = 0;
        bbr_init_pacing_rate_from_rtt(sk);
    	
        cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED);
    }
    int sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
    {
        switch (optname) {
        case SO_MAX_PACING_RATE:
            if (val != ~0U)
                cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED);
            sk->sk_max_pacing_rate = val;
            sk->sk_pacing_rate = min(sk->sk_pacing_rate, sk->sk_max_pacing_rate);
            break;
        }
    }

    Pacing功能启用


    内核网络中的流控算法Fair queue可以很好的完成数据报文的pacing功能,但是当前系统为其网络接口选用了sch_fq的算法。所以,不考虑流控系统,函数tcp_needs_internal_pacing检查是否要在TCP子系统中执行pacing功能。

    static bool tcp_needs_internal_pacing(const struct sock *sk)
    {
        return smp_load_acquire(&sk->sk_pacing_status) == SK_PACING_NEEDED;
    }

    函数tcp_internal_pacing负责开启TCP自身的pacing功能,前提是由pacing的需求,并且当前速率不为零而且不等于最大无符号整数值。按照当前的速率计算发送skb数据包所需要的时长,以纳秒为单位。启动pacing_timer,超时时间设置为当前数据包以设定的pacing速率发送完成所需的时长。但是,实际情况中,数据可能并不需要如此长的时间。

    static void tcp_internal_pacing(struct sock *sk, const struct sk_buff *skb)
    {
        u64 len_ns;
        u32 rate;
    
        if (!tcp_needs_internal_pacing(sk))
            return;
        rate = sk->sk_pacing_rate;
        if (!rate || rate == ~0U)
            return;
    
        /* Should account for header sizes as sch_fq does, but lets make things simple. */
        len_ns = (u64)skb->len * NSEC_PER_SEC;
        do_div(len_ns, rate);
        hrtimer_start(&tcp_sk(sk)->pacing_timer, ktime_add_ns(ktime_get(), len_ns), HRTIMER_MODE_ABS_PINNED);
    }

    以上pacing使能函数tcp_internal_pacing在TCP传输函数tcp_transmit_skb中调用,条件是当前发送的数据包有数据,并非是SYN或者ACK类的控制报文。鉴于此时skb还没有添加网络层以及链路层的头部信息,tcp_internal_pacing在计算数据包发送时长算法中,也仅是TCP头部和数据的长度。但是流控系统的sch_fq算法不同,其可获取到最终的完整数据包长度。

    static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask)
    {
        if (skb->len != tcp_header_size) {
            tcp_event_data_sent(tp, sk);
            tp->data_segs_out += tcp_skb_pcount(skb);
            tcp_internal_pacing(sk, skb);
        }
    }

    Pacing检查


    Pacing功能的检查一个是之前的tcp_needs_internal_pacing函数,检查TCP pacing是否开启;另一个条件是pacing定时器是否启动,由函数hrtimer_active实现。两个条件同时成立,表明pacing正在工作,暂停数据包的发送。

    static bool tcp_pacing_check(const struct sock *sk)
    {
        return tcp_needs_internal_pacing(sk) && hrtimer_active(&tcp_sk(sk)->pacing_timer);
    }

    参见以下的TCP发送队列处理函数tcp_write_xmit和重传队列处理函数tcp_xmit_retransmit_queue,在tcp_pacing_check返回真表明pacing已在工作时,退出发送处理流程。

    static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, int push_one, gfp_t gfp)
    {
        max_segs = tcp_tso_segs(sk, mss_now);
        while ((skb = tcp_send_head(sk))) {
    
            if (tcp_pacing_check(sk))
                break;
    }
    void tcp_xmit_retransmit_queue(struct sock *sk)
    {
        rtx_head = tcp_rtx_queue_head(sk);
        skb = tp->retransmit_skb_hint ?: rtx_head;
        max_segs = tcp_tso_segs(sk, tcp_current_mss(sk));
        skb_rbtree_walk_from(skb) {
    
            if (tcp_pacing_check(sk))
                break;
    }

    Pacing处理


    由于tcp_pacing_check函数和tcp_internal_pacing函数配合,已经实现了TCP数据包的pacing功能。在pacing定时器超时之后,pacing超时处理函数tcp_pace_kick其实并不需要做什么处理了。

    但是在实现中,tcp_pace_kick函数将处理被TSQ(TCP Small Queue)功能设置为阻塞状态的套接口。关于TSQ参见 https://blog.csdn.net/sinat_20184565/article/details/89341370。

    static bool tcp_small_queue_check(struct sock *sk, const struct sk_buff *skb, unsigned int factor)
    {
        limit = max(2 * skb->truesize, sk->sk_pacing_rate >> sk->sk_pacing_shift);
        limit = min_t(u32, limit, sock_net(sk)->ipv4.sysctl_tcp_limit_output_bytes);
        limit <<= factor;
    }

    由于在TSQ检查时,可能由于当前的pacing速率sk_pacing_rate过高,TSQ限制了数据报文的发送,将套接口设置为阻塞状态。所以,在tcp_pace_kick函数中处理TSQ队列,必要时调用TSQ的tasklet处理。

    enum hrtimer_restart tcp_pace_kick(struct hrtimer *timer)
    {
        struct tcp_sock *tp = container_of(timer, struct tcp_sock, pacing_timer);
        struct sock *sk = (struct sock *)tp;
    
        for (oval = READ_ONCE(sk->sk_tsq_flags);; oval = nval) {
            struct tsq_tasklet *tsq;
            bool empty;
    
            if (oval & TSQF_QUEUED)
                break;
    
            nval = (oval & ~TSQF_THROTTLED) | TSQF_QUEUED | TCPF_TSQ_DEFERRED;
            nval = cmpxchg(&sk->sk_tsq_flags, oval, nval);
            if (nval != oval)
                continue;
    
            if (!refcount_inc_not_zero(&sk->sk_wmem_alloc))
                break;
            /* queue this socket to tasklet queue */
            tsq = this_cpu_ptr(&tsq_tasklet);
            empty = list_empty(&tsq->head);
            list_add(&tp->tsq_node, &tsq->head);
            if (empty)
                tasklet_schedule(&tsq->tasklet);
            break;
        }
        return HRTIMER_NORESTART;
    }

    Pacing速率

    速率更新的基础函数为tcp_update_pacing_rate。如下,当前pacing速率的计算由三个变量组成。当前发送MSS缓存值mss_cache乘以拥塞窗口cwnd的结果,除以平滑往返时间srtt,及最大可发送的数据长度除以srtt得到当前pacing速率。拥塞窗口函数中是取自发送拥塞窗口值snd_cwnd与已发出数据包数量packets_out两者之中的最大值。对于处在慢启动阶段的套接口,将得到的速率值默认增加200%倍(sysctl_tcp_pacing_ss_ratio);反之对于处在拥塞避免阶段的套接口,将速率默认增加120%倍(sysctl_tcp_pacing_ca_ratio)。最终的pacing速率不能大于限定的最大值sk_max_pacing_rate。

    static void tcp_update_pacing_rate(struct sock *sk)
    {
        /* set sk_pacing_rate to 200 % of current rate (mss * cwnd / srtt) */
        rate = (u64)tp->mss_cache * ((USEC_PER_SEC / 100) << 3);
    
        /* current rate is (cwnd * mss) / srtt
         * In Slow Start [1], set sk_pacing_rate to 200 % the current rate.
         * In Congestion Avoidance phase, set it to 120 % the current rate.
         *
         * [1] : Normal Slow Start condition is (tp->snd_cwnd < tp->snd_ssthresh)
         *   If snd_cwnd >= (tp->snd_ssthresh / 2), we are approaching end of slow start and should slow down.
         */
        if (tp->snd_cwnd < tp->snd_ssthresh / 2)
            rate *= sock_net(sk)->ipv4.sysctl_tcp_pacing_ss_ratio;
        else
            rate *= sock_net(sk)->ipv4.sysctl_tcp_pacing_ca_ratio;
    
        rate *= max(tp->snd_cwnd, tp->packets_out);
    
        if (likely(tp->srtt_us))
            do_div(rate, tp->srtt_us);
    
        /* WRITE_ONCE() is needed because sch_fq fetches sk_pacing_rate
         * without any lock. We want to make sure compiler wont store intermediate values in this location.
         */
        WRITE_ONCE(sk->sk_pacing_rate, min_t(u64, rate, sk->sk_max_pacing_rate));
    }

    Pacing速率更新的入口有两个。一个位于TCP服务端接收到客户端的三次握手的ACK报文之后,初始化pacing速率。前提是TCP当前采用的拥塞避免算法没有实现cong_control回调函数,目前仅有BBR算法实现了此回调。

    int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
    {
        switch (sk->sk_state) {
        case TCP_SYN_RECV:
            if (!inet_csk(sk)->icsk_ca_ops->cong_control)
                tcp_update_pacing_rate(sk);
    }
    static struct tcp_congestion_ops tcp_bbr_cong_ops __read_mostly = {
        .flags      = TCP_CONG_NON_RESTRICTED,
        .name       = "bbr",
        .cong_control   = bbr_main,
    };

    如果采用BBR算法,将不在tcp_rcv_state_process函数中初始化pacing速率。BBR拥塞算法在cong_control回调(bbr_main)中设置pacing速率,如下的tcp_cong_control函数,如果cong_control有值,执行完之后就结束函数。只有在采用除BBR算法之外的其它拥塞算法时(cong_control为空指针),才会往后执行,调用pacing速率更新函数。tcp_cong_control函数在处理ACK确认报文的最后被调用。

    static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked, int flag, const struct rate_sample *rs)
    {
        const struct inet_connection_sock *icsk = inet_csk(sk);
    
        if (icsk->icsk_ca_ops->cong_control) {
            icsk->icsk_ca_ops->cong_control(sk, rs);
            return;
        }
        if (tcp_in_cwnd_reduction(sk)) {  
            tcp_cwnd_reduction(sk, acked_sacked, flag);   /* Reduce cwnd if state mandates */
        } else if (tcp_may_raise_cwnd(sk, flag)) {
            tcp_cong_avoid(sk, ack, acked_sacked);        /* Advance cwnd if state allows */
        }
        tcp_update_pacing_rate(sk);
    }
    static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
    {
        tcp_cong_control(sk, ack, delivered, flag, sack_state.rate);
        tcp_xmit_recovery(sk, rexmit);
        return 1;
    }

    BBR调整pacing速率

    在拥塞控制算法BBR中,将cong_control回调初始化为指向bbr_main函数的指针,其调用bbr_set_pacing_rate函数更新pacing速率。

    static void bbr_main(struct sock *sk, const struct rate_sample *rs)
    {
        struct bbr *bbr = inet_csk_ca(sk);
    
        bbr_update_model(sk, rs);
        bw = bbr_bw(sk);
        bbr_set_pacing_rate(sk, bw, bbr->pacing_gain);
    }

    BBR中核心的pacing速率计算函数如下的bbr_rate_bytes_per_sec。其中rate参数为BBR算法估算出的当前带宽值,其乘以MTU值,再乘以一个增益值gain,类似于之前接收的函数tcp_update_pacing_rate中计算pacing速率的过程,差别在与这里使用的报文长度为MTU(不包括链路层长度),并且将之前的拥塞窗口换成了带宽值,而且将之前的递增倍率(sysctl_tcp_pacing_ss_ratio/sysctl_tcp_pacing_ca_ratio)换成了BBR计算的增益值gain。

    函数bbr_bw_to_pacing_rate确保pacing值不超过最大的限定值sk_max_pacing_rate。

    static u64 bbr_rate_bytes_per_sec(struct sock *sk, u64 rate, int gain)
    {
        rate *= tcp_mss_to_mtu(sk, tcp_sk(sk)->mss_cache);
        rate *= gain;
        rate >>= BBR_SCALE;
        rate *= USEC_PER_SEC;
        return rate >> BW_SCALE;
    }
    /* Convert a BBR bw and gain factor to a pacing rate in bytes per second. */
    static u32 bbr_bw_to_pacing_rate(struct sock *sk, u32 bw, int gain)
    {
        u64 rate = bw;
    
        rate = bbr_rate_bytes_per_sec(sk, rate, gain);
        rate = min_t(u64, rate, sk->sk_max_pacing_rate);
        return rate;
    }  

    在主回调函数bbr_main中,bbr_set_pacing_rate调用以上bbr_bw_to_pacing_rate获取到pacing的速率值。通常情况下has_seen_rtt变量已经置位,在初始化函数bbr_init中已调用过函数bbr_init_pacing_rate_from_rtt。如果带宽已占满,或者计算的pacing速率大于当前使用的速率sk_pacing_rate,更新当前速率。

    为了在保证较少队列的同时维持网络的高利用率和低延时,计算所得的pacing速率略低于估算带宽的百分之一左右,为达到此目的,在计算pacing速率时,使用了链路的MTU值,未将链路层头部数据长度包含在内。

    static void bbr_set_pacing_rate(struct sock *sk, u32 bw, int gain)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct bbr *bbr = inet_csk_ca(sk);
        u32 rate = bbr_bw_to_pacing_rate(sk, bw, gain);
    
        if (unlikely(!bbr->has_seen_rtt && tp->srtt_us))
            bbr_init_pacing_rate_from_rtt(sk);
        if (bbr_full_bw_reached(sk) || rate > sk->sk_pacing_rate)
            sk->sk_pacing_rate = rate;
    }

    函数bbr_init_pacing_rate_from_rtt已在br_init初始化时调用,带宽的值由发送拥塞窗口乘以BW_UNIT,在除以rtt_us值得到,通过以上的bbr_bw_to_pacing_rate函数计算pacing速率,增益值使用bbr_high_gain。如果平滑往返时间为零,rtt_us使用缺省的USEC_PER_MSEC(1000),反之,使用srtt_us值除以8(BBR_SCALE)的值。
     

    #define BW_SCALE 24
    #define BW_UNIT (1 << BW_SCALE)
    #define BBR_SCALE 8                /* scaling factor for fractions in BBR (e.g. gains) */
    #define BBR_UNIT (1 << BBR_SCALE)
    static const int bbr_high_gain  = BBR_UNIT * 2885 / 1000 + 1;
     
    /* Initialize pacing rate to: high_gain * init_cwnd / RTT. */
    static void bbr_init_pacing_rate_from_rtt(struct sock *sk)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct bbr *bbr = inet_csk_ca(sk);
        u64 bw;
        u32 rtt_us;
    
        if (tp->srtt_us) {      /* any RTT sample yet? */
            rtt_us = max(tp->srtt_us >> 3, 1U);
            bbr->has_seen_rtt = 1;
        } else {             /* no RTT sample yet */
            rtt_us = USEC_PER_MSEC;  /* use nominal default RTT */
        }
        bw = (u64)tp->snd_cwnd * BW_UNIT;
        do_div(bw, rtt_us);
        sk->sk_pacing_rate = bbr_bw_to_pacing_rate(sk, bw, bbr_high_gain);
    }

     

    内核版本 4.15

     

    展开全文
  • 之前一直也用pacing值来调节TPS,一直觉得它和thinktime没啥区别.这次项目中,和同事就此展开了讨论,细细一研究发现pacing值门道还是很多的.  如下面三个图:  上图是pacing的三个选项,如果选第一项...
  • Loadrunner pacing说明

    2020-03-23 15:49:45
    在使用loadrunner进行性能测试时,为了控制发送请求的速度,避免服务端处理队列产生堵塞,往往使用pacing设置每次迭代之间的时间间隔。注意:pacing 和 think time是不同的概念,前者是设置每次迭代之间的时间间隔,...

    在使用loadrunner进行性能测试时,为了控制发送请求的速度,避免服务端处理队列产生堵塞,往往使用pacing设置每次迭代之间的时间间隔。注意:pacing 和 think time是不同的概念,前者是设置每次迭代之间的时间间隔,而后者则是设置每个请求之间的时间间隔。

    在virtual user generator脚本生成器中点击run-time settings 即可打开如下设置界面:
    在这里插入图片描述
    pacing的设置主要是针对如何开始新的迭代,方式有三种:

    1. As soon as the previous iteration ends(上一次迭代一结束就开始);
      即loadrunner在收到第一次迭代的返回后立刻开始下一次迭代。
      在这里插入图片描述

    2. After the previous iteration ends(上一次迭代结束后):
      即在上一次迭代结束后,经过一个固定的或随机的时间间隔后再开始第二次迭代;
      在这里插入图片描述

    3. At fixed/random intervals,every * sec
      在这里插入图片描述
      注意:这个间隔时间指得是每次迭代开始之间的时间间隔
      在这里插入图片描述
      这里需要说明一下:
      a. 当设置的pacing间隔大于每次迭代的响应时间,则两次迭代开始之间的间隔就是pacing间隔。
      在这里插入图片描述
      b. 当设置的pacing间隔小于每次迭代的响应时间,则两次迭代开始之间的间隔就是上一次迭代的响应时间;
      在这里插入图片描述

    展开全文
  • webrtc的pacing分析

    千次阅读 2017-12-07 17:54:56
    webrtc中pacing模块的分析

    If you can’t explain it simply, you don’t understand it well enough.-Albert Einstein

     根据流量对于时延的敏感性,可以把数据流分为弹性流(elastic traffic)和非弹性流(inelastic traffic)。所谓的时延是否敏感,衡量的标准就是数据流是否能够在时间轴上拉伸。RTP的实时视频流属于非弹性流,而文件传输则是弹性流。
     不管什么流,数据流的发送速率应该同网络的分配给相应数据流可用带宽(available bandwidth)相匹配。关于pacing在网络中的作用,参考[1].关于数据流的pacing,有一些相关研究,一个反对[2],一个赞成[3].基于在youtube上的实验结果[5],表明pacing降低丢包率,降低rtt。在BBR里面,就有一个pacing速率sk_pacing_rate。在我看来,网卡向外发送几个数据包,几乎是不占什么时间的,但是pacing的存在,需要将数据包的发送时间"拉伸"。比如在BBR中,瞬时发送一个数据包(1500Byte)后,需要等待一段时间 Δ t = 1500 B y t e p a c i n g _ r a t e \Delta t=\frac{1500Byte}{pacing\_rate} Δt=pacing_rate1500Byte之后,再发送下一个数据包,。这样就可以保证,最后数据的发送速率为pacing_rate。
     接下来说下webrtc中的pacing的大致原理paced_sender.cc。可能有不准的地方。
     类 IntervalBudget中主要两个变量,发送速率(target_rate_kbps_),允许向外发送的字节(bytes_remaining_)。线程池每5ms(kMinPacketLimitMs)进入PacedSender::Process处理一次。假设发送的速率设定为200KB/s,假设一个数据包长度为1500Byte。那么每隔5ms可以向外发送1000byte的数据,但是实际上pacing模块按照数据包(1500Byte)为单位向网络中发送数据包。只要bytes_remaining_>0。上个时间间隔若"多发"了字节,需要在本次时间间隔内"少发”(bytes_remaining_ = bytes_remaining_ + bytes)。

      void IncreaseBudget(int64_t delta_time_ms) {
        int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;
        if (bytes_remaining_ < 0) {
          // We overused last interval, compensate this interval.
          bytes_remaining_ = bytes_remaining_ + bytes;
        } else {
          // If we underused last interval we can't use it this interval.
          bytes_remaining_ = bytes;
        }
      }
      void UseBudget(size_t bytes) {
        bytes_remaining_ = std::max(bytes_remaining_ - static_cast<int>(bytes),
                                    -kWindowMs * target_rate_kbps_ / 8);
      }
    
    

     那么发包序号如下,前面为时刻,括号内为发送出去的包序号,0ms(bytes_remaining=1000),5ms(包1, bytes_remaining_=-500),10ms(包2,bytes_remaining_=-1000),15ms(不发,bytes_remaining_=0),20ms(包4,bytes_remaining_=-500)……就是每隔15ms向外发送两个数据包。
     但是视频数据的产生是按照视频帧率产生的,而通过paceing模块处理,数据包的发送速率是按照target_rate_kbps_发送的。这样数据包就要在发送缓冲区中排队。排队造成了时延。数据时延超过了一定的阈值,下面的代码,就要采取冒险策略,增加发送速率。

    void PacedSender::Process() {
      if (!paused_ && elapsed_time_ms > 0) {
        size_t queue_size_bytes = packets_->SizeInBytes();
        if (queue_size_bytes > 0) {
          // Assuming equal size packets and input/output rate, the average packet
          // has avg_time_left_ms left to get queue_size_bytes out of the queue, if
          // time constraint shall be met. Determine bitrate needed for that.
          packets_->UpdateQueueTime(clock_->TimeInMilliseconds());
          int64_t avg_time_left_ms = std::max<int64_t>(
              1, kMaxQueueLengthMs - packets_->AverageQueueTimeMs());//队列中的最大
              //延时kMaxQueueLengthMs为2000ms,队列时延较大,avg_time_left_ms较小
          int min_bitrate_needed_kbps =
              static_cast<int>(queue_size_bytes * 8 / avg_time_left_ms);
          if (min_bitrate_needed_kbps > target_bitrate_kbps)//超出了之前的速率,
          //就要增加速率
            target_bitrate_kbps = min_bitrate_needed_kbps;
        }
        media_budget_->set_target_rate_kbps(target_bitrate_kbps);
        elapsed_time_ms = std::min(kMaxIntervalTimeMs, elapsed_time_ms);
        UpdateBudgetWithElapsedTime(elapsed_time_ms);
      }
    }
    

     其计算数据包在PacketQueue中的排队时延的方法很有意思。

    class PacketQueue {
     public:
      explicit PacketQueue(const Clock* clock)
          : bytes_(0),
            clock_(clock),
            queue_time_sum_(0),
            time_last_updated_(clock_->TimeInMilliseconds()) {}
      virtual ~PacketQueue() {}
    
      void Push(const Packet& packet) {
        if (!AddToDupeSet(packet))
          return;
    
        UpdateQueueTime(packet.enqueue_time_ms);
    
        // Store packet in list, use pointers in priority queue for cheaper moves.
        // Packets have a handle to its own iterator in the list, for easy removal
        // when popping from queue.
        packet_list_.push_front(packet);
        std::list<Packet>::iterator it = packet_list_.begin();
        it->this_it = it;          // Handle for direct removal from list.
        prio_queue_.push(&(*it));  // Pointer into list.
        bytes_ += packet.bytes;
      }  
      void FinalizePop(const Packet& packet) {
        RemoveFromDupeSet(packet);
        bytes_ -= packet.bytes;
        queue_time_sum_ -= (time_last_updated_ - packet.enqueue_time_ms);//(2)更新排
       // 队时延
        packet_list_.erase(packet.this_it);
        RTC_DCHECK_EQ(packet_list_.size(), prio_queue_.size());
        if (packet_list_.empty())
          RTC_DCHECK_EQ(0, queue_time_sum_);
      }
      void UpdateQueueTime(int64_t timestamp_ms) {
        RTC_DCHECK_GE(timestamp_ms, time_last_updated_);
        int64_t delta = timestamp_ms - time_last_updated_;
        // Use packet packet_list_.size() not prio_queue_.size() here, as there
        // might be an outstanding element popped from prio_queue_ currently in the
        // SendPacket() call, while packet_list_ will always be correct.
        queue_time_sum_ += delta * packet_list_.size();//(1)计算数据包的排队时延
        time_last_updated_ = timestamp_ms;
      }
    }
    

     每次上层向PacketQueue中Push数据包,需要调用一次UpdateQueueTime来更新下时戳,并计算下排队时延(上面代码的标号(1))。线程池每次进入PacedSender::Process函数,也需要更新下排队的时延(packets_->UpdateQueueTime(clock_->TimeInMilliseconds()))。本质就是向PacketQueue注入时间的变化(delta)。
     以下图为例,横轴表示时间的流逝,上面的竖线,表示上层向队列中Push的数据包。其中t1,t2,t3表示PacedSender::Process函数的调动间隔,假设插入m个数据包,数据包成员packet.enqueue_time_ms记录进入队列的时间。假设数据包之间没有间隔(实际上有很小的间隔也不影响分析)。在t1时刻,线程进入PacedSender::Process函数,队列为空,但是需要更新time_last_updated_为t1。瞬时插入m个包后, queue_time_sum_为0;在t2时刻,进入PacedSender::Process函数,更新time_last_updated_为t2,queue_time_sum_=m*(t2-t1),发送出一个数据包后(最先进入队列的数据包),调用FinalizePop,更新排队时延(标号(2)),queue_time_sum_=m*(t2-t1)-t1。之后又插入n个数据包。在t3时刻,进入PacedSender::Process函数,更新time_last_updated_为t3,更新queue_time_sum_=(m-1)(t2-t1)+(m+n-1)(t3-t2)。代码中标号(1)计算的就是已有的数据包在之前时间间隔内的排队时间与新的时间间隔的排队时延。在Push函数中,是先更新队列时戳,再改变队列的长度(新入队的包还不需要排队时延)。实际上数据包的的入队可能在时间间隔内的任何时刻,但是不影响结果。
    PacketQueue
    [1]BBR与CoDel
    [2]Aggarwal A, Savage S, Anderson T. Understanding the performance of TCP pacing[C]//INFOCOM 2000. Nineteenth Annual Joint Conference of the IEEE Computer and Communications Societies. Proceedings. IEEE. IEEE, 2000, 3: 1157-1165.
    [3]Wei D, Cao P, Low S, et al. TCP pacing revisited[C]//Proceedings of IEEE INFOCOM. 2006.
    [4]Carousel: Scalable Traffic Shaping at End Hosts(2017-sigcomm)
    [5]Trickle: Rate Limiting YouTube Video Streaming(2012)
    [6]A Model Predictive Control Approach to Flow Pacing for TCP(2017)

    展开全文
  • 前一篇写了关系rtb bidding stragegy的相关内容,这一篇主要介绍的是RTB竞价中的流控策略:budget pacing,主要内容是让广告主预算平稳花完,防止出现预算突然花完这种情况。[注:本笔记主要围绕着Display ...
  • Loadrunner-Pacing步长

    2019-03-15 16:04:33
    打开Loadrunner 》Vuser 》Run-time Settings 》General 》Pacing 第一项:As soon as the previous iteration ends 无间隔 第二项:After the previous iteration ends:在上一个迭代结束后 ① With a fixed ...
  • loadrunner的pacing

    2015-06-23 17:47:06
    The pacing let you control the time between iterations. Thepace tells the Vuser how long to wait between iterations of your actions。  Vuser think time emulates the time that a real user waitsbetween
  • WebRTC中的pacing模块主要负责拥塞控制,其中RoundRobinPacketQueue是模块里的核心数据结构,项目开发中也要做类似的拥塞控制模块,WebRTC中的RoundRobinPacketQueue设计思路也很值得借鉴。 1、模块定义 ...
  • 在loadrunner里,有两个概念很容易被混淆:pacing和think time。相关书籍中很难查阅到对pacing的讲解。这两个名词该如何理解,如何应用呢? 它们都出现loadrunner VUGen的run-time settings里,是不同的概念。先...
  • loadrunner中pacing设置01

    2016-07-06 21:32:00
    之前一直也用pacing值来调节TPS,一直觉得它和think time没啥区别.这次项目中,和同事就此展开了讨论,细细一研究发现pacing值门道还是很多的.  如下面三个图:  上图是pacing的三个选项,如果选第一项 "As soon as...
  • 首先看看下面的tcptrace图: 我的配置如下: 初始窗口:4 mss 拥塞控制算法:BBR qdisc:fq ...有原教旨般洁癖的看到...对于这个gap,我的意思就是,为了bbr启动曲线的优美平滑无毛刺,初始的pacing rate不再乘以...
  • OSPF Update Packet-Pacing Configurable Timers Feature History Release Modification 12.2(4)T This feature was introduced. 12.2(4)T3 Support for the Cisco 7500 series was added in Cisco IOS Release 12.
  • LOADRUNNER中PACING的设置

    2015-04-22 16:17:53
    在 LoadRunner 的运行场景中,有一个不大起眼的设置,可能经常会被很多人忽略,它就是Pacing 。具体设置方式为: Run-Time settings à General à Pacing ,这个设置的功能从字面上就很容易理解,即在场景...
  • Matlab的耳语Pacing_spike_removal 半自动化的心电图起搏伪像检测和消除算法 表中的内容 MATLAB代码 STATA代码 测试文件 作者 卡齐·哈克(Kazi Haq)博士 内拉吉·Java德卡(Neeraj Javadekar) 医学博士Larisa ...
  • 在混合压力测试场景和稳定性测试场景中,我们需要配置pacing值,以保证各支交易按占比向服务器发压力,符合生产上的情况。 预期TPS通过需求给出的交易量计算得出,参考《性能测试准备——计算TPS》。不同的项目要求...
  • 在loadrunner测试过程中,需要用到思考时间这一概念,本文章介绍pacing和think time之间的区别
  • loadrunner的pacing-2

    2015-06-23 17:49:45
    Pacing: 在场景的两次迭代 (iteration) 之间,加入一个时间间隔(步进) 这个设置到底有什么作用?为什么要进行这个设置? 在 LoadRunner 的运行场景中,有一个不大起眼的设置,可能经常会被很多人忽略,它就是 ...
  • 要是在没有认识think time的情况下来了解Pacing(步长)的话,我想所有人一点就明白,由于以前使用最多是think time,所以现在了解Pacing来有点吃力,不是我笨,是因为使用惯了所以很些地方没有从另一个角度去思考。...
  • TCP Pacing From:   Daniele Lacamera To:   Stephen Hemminger , "David S. Miller" Subject:   TCP Pacing Date:   Tue, 12 Sep 2006 19:58:21 +0200 Cc:   netdev...
  • 彻底实现Linux TCP的Pacing发送逻辑-高精度hrtimer版

    万次阅读 热门讨论 2017-01-14 08:36:17
     如果单纯的将《彻底实现Linux TCP的Pacing发送逻辑-普通timer版》中的timer_list换成hrtimer,必然招致失败。因为在hrtimer的function中,调用诸如tcp_write_xmit这样的长路径函数是一种用丝袜装榴莲的行为。好吧...
  • 在LoadRunner的运行场景中,有一个不大起眼的设置,可能经常会被很多人忽略,它就是 Pacing 。具体设置方式为: Run-Time settings à General à Pacing ,这个设置的功能从字面上就很容易理解,即在场景的两次迭代...
  • 谈谈LOADRUNNER中PACING的设置

    千次阅读 2015-11-30 10:46:08
    原作者:xingcyx  ... 在 LoadRunner 的运行场景中,有一个不大起眼的设置,可能经常会被很多人忽略,它就是Pacing...具体设置方式为: Run-Time settings à General à Pacing ,这个设置的功能从字面上就
  • Pacing时间的设置需要根据使用您系统的用户的行为来决定。 如果您那边的用户在您的系统上做完一套操作后不会做下一套,则可能不需使用Pacing。 如果您那边用户在系统上需要不断地做同样的操作,比如他要反复的浏览...
  • 问题描述 在编译sdk的时候,出现sk_pacing_shift_update未定义问题,导致编译失败
  • 运行时的Pacing设置主要影响什么?  Pacing主要用来设置重复迭代脚本的间隔时间。共有三种方法: A:上次迭代结束后立刻开始、  B:上次迭代结束后等待固定时间、 C:按固定或随机的时间间隔开始执行新的迭代。---...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,272
精华内容 908
关键字:

pacing