Uperf是一个网络性能测试工具。这篇文章主要讲解一下其中的实现细节,基本的用法清参考。
Sun Fire T5140采用了两片UltraSPARC T2 Plus CPU,也就是说这一台1U的机器就能够有128个线程(2CPU * 8核 * 8线程)。其中内置了4口e1000g千兆网卡和两口的光线ixgbe 10G网卡。其中的10G网卡最多可以有32条发送I/O rings和64条接收I/O rings。如果CPU线程数量不够,网卡的高性能是绝对发挥不出来的。即使其他1U的机器能够使10G网卡达到线速,CPU占用率也是非常高的。要想证明这一点,强大的测试工具是少不了的。
Netperf/netserver是大家广为使用的测试工具,但是它只能测试单个线程/进程的性能。要想测试多进程的性能必须起多个实例,测试多线程更是做不到。而测试多连接性能带来的问题是,如何让这多个进程在建立完连接后同时发送/接收数据,如何同时结束。MAXQ解决了这个问题,但是有可能涉及专利问题,不便提及。如果不能同时发送/接收数据,先建立连接的进程占用了全部带宽,同理,后结束的进程也会占用全部带宽,使得最后累加的数据高于理论值,uperf正解决了这个问题。
Uperf可以实时更新当前测试的统计数据。要想实现这一点,uperf让各个进程/线程通过mmap共享结构体struct uperf_shm *global_shm,这样每个进程/线程更新完自己的数据后,master主线程就可以每过一秒统计所有进程/线程的数据。
function runuperf
{
export NETPERF_XML=netperf.xml
export host1=11.0.1.153 dur=600 nth=1000 msg=8192 wnd=64K pro=tcp
uperf -m ./$NETPERF_XML
}
上面的例子建立1000个线程进行数据发送测试。如果group的属性不是nthreads,而是nprocs则建立进程进行测试。如transaction的属性是iterations则该transaction执行若干次后停止;如果是duration,则执行若干秒后停止。刚才提到了uperf解决了各个线程/进程之间同步的问题,它主要是通过读写锁的机制来实现的。上面的实例一共有三个transactions,这三个transactions通过一个单项链表连接起来。第一个线程执行完它的第一个transactions后,将gloabl_shm->bar[0]->count原子加一。
(void) atomic_add_32_nv(&bar->count, 1);
然后pthread_rwlock_rdlock(&bar->barrier),而之前master主线程已经将写锁捕获,所以第一个线程会休眠。同理其他的进程/线程执行完第一个transaction后也会休眠。master每过一秒就检查一下bar->count是不是等于bar->limit。而bar->limit被初始化为当前transactions的进程/线程数,也就是检查是不是所有的进程/线程执行完了第一个transaction,如果为真,则释放写锁,唤醒所有进程/线程以及slaves执行下一个transaction。
在上面的实例中dur=600,也就是说第二个transaction执行10分钟。首先uperf给每个进程/线程注册一个global_shm->callouts表,这个表记录了每个进程/线程从现在算起什么时候退出。然后每个进程/线程进入一个while(0)循环,反复顺序执行每个flowop规定的动作,这里是发送操作。master主线程每过一秒就会检查一下各个进程/线程注册的时间是不是到了,如果到了就发送一个SIGUSR2信号,进程/线程收到后将gloabl_shm->bar[i]->count原子加一,捕获读锁休眠。后面就同上了。
如果要禁掉Nagle's algorithm,需要在建立连接的时候在options中加入tcp_nodelay。用下面的dtrace脚本会发现,如果加上了tcp_nodelay,然后export msg=1,则包的大小是66个字节;如果不加,TCP/IP协议栈会将包聚合为90个字节大小的包后才发送出去,提高了性能,但是没有真实测到网卡发送小包的性能。
#!/usr/sbin/dtrace -qs
fbt:bge:bge_send:entry
/args[1]->b_rptr[4] == 0x1a && args[1]->b_rptr[5] == 0x14 && args[1]->b_rptr[12] == 0x08 && args[1]->b_rptr[13] == 0x0 && args[1]->b_rptr[23] == 0x6/
{
printf("Message size: %d\n", args[1]->b_wptr - args[1]->b_rptr);
printf("==================ETHER==================\n");
printf("Destination: ");
printf("%x:", args[1]->b_rptr[0]);
printf("%x:", args[1]->b_rptr[1]);
printf("%x:", args[1]->b_rptr[2]);
printf("%x:", args[1]->b_rptr[3]);
printf("%x:", args[1]->b_rptr[4]);
printf("%x\t", args[1]->b_rptr[5]);
printf("Source: ");
printf("%x:", args[1]->b_rptr[6]);
printf("%x:", args[1]->b_rptr[7]);
printf("%x:", args[1]->b_rptr[8]);
printf("%x:", args[1]->b_rptr[9]);
printf("%x:", args[1]->b_rptr[10]);
printf("%x\t", args[1]->b_rptr[11]);
ether_type = (args[1]->b_rptr[12] & 0xff) << 8 | (args[1]->b_rptr[13] & 0xff);
printf("Ether type: ");
printf("%4x\n", ether_type);
printf("==================IP==================\n");
printf("Version: ");
printf("%d\t", (args[1]->b_rptr[14] & 0xf0) >> 4);
printf("Header length: ");
printf("%d\t", args[1]->b_rptr[14] & 0x0f);
total_length = (args[1]->b_rptr[16] & 0xff) << 8 | (args[1]->b_rptr[17] & 0xff);
printf("Total length: ");
printf("%d\n", total_length);
/*
printf("%x\n", args[1]->b_rptr[18]);
printf("%x\n", args[1]->b_rptr[19]);
printf("%x\n", args[1]->b_rptr[20]);
printf("%x\n", args[1]->b_rptr[21]);
*/
printf("Time to Live: ");
printf("%d\t", args[1]->b_rptr[22]);
printf("Protocal (ICMP=1, TCP=6, UDP=17): ");
printf("%d\t", args[1]->b_rptr[23]);
printf("Header Checksum: ");
checksum = (args[1]->b_rptr[24] & 0xff) << 8 | (args[1]->b_rptr[25] & 0xff);
printf("%x\n", checksum);
printf("Source Address: ");
printf("%d.", args[1]->b_rptr[26]);
printf("%d.", args[1]->b_rptr[27]);
printf("%d.", args[1]->b_rptr[28]);
printf("%d\n", args[1]->b_rptr[29]);
printf("Destination Address: ");
printf("%d.", args[1]->b_rptr[30]);
printf("%d.", args[1]->b_rptr[31]);
printf("%d.", args[1]->b_rptr[32]);
printf("%d\n", args[1]->b_rptr[33]);
printf("==================TCP==================\n");
printf("Source port: ");
port = (args[1]->b_rptr[34] & 0xff) << 8 | (args[1]->b_rptr[35] & 0xff);
printf("%d\t", port);
port = (args[1]->b_rptr[36] & 0xff) << 8 | (args[1]->b_rptr[37] & 0xff);
printf("Destination port: ");
printf("%d\n", port);
printf("Sequence number: ");
seq = (args[1]->b_rptr[38] & 0xff) << 8;
seq = seq | ((args[1]->b_rptr[39] & 0xff)) << 8;
seq = seq | ((args[1]->b_rptr[40] & 0xff)) << 8;
seq = seq | (args[1]->b_rptr[41] & 0xff);
printf("%d\n", seq);
printf("Acknowledgment number: ");
ack = (args[1]->b_rptr[42] & 0xff) << 8;
ack = ack | ((args[1]->b_rptr[43] & 0xff) << 8);
ack = ack | ((args[1]->b_rptr[44] & 0xff) << 8);
ack = ack | (args[1]->b_rptr[45] & 0xff);
printf("%d\n", ack);
/*
printf("%x\n", args[1]->b_rptr[46]);
printf("%x\n", args[1]->b_rptr[47]);
*/
printf("Window Size: ");
window = (args[1]->b_rptr[48] & 0xff) << 8 | (args[1]->b_rptr[49] & 0xff);
printf("%d\n", window);
printf("Checksum: ");
checksum = (args[1]->b_rptr[50] & 0xff) << 8 | (args[1]->b_rptr[51] & 0xff);
printf("%x\n", checksum);
/*
printf("%x\n", args[1]->b_rptr[52]);
printf("%x\n", args[1]->b_rptr[53]);
*/
printf("Data begin:\n");
printf("%x ", args[1]->b_rptr[54]);
printf("%x ", args[1]->b_rptr[55]);
printf("%x ", args[1]->b_rptr[56]);
printf("%x ", args[1]->b_rptr[57]);
printf("%x ", args[1]->b_rptr[58]);
printf("%x ", args[1]->b_rptr[59]);
printf("%x ", args[1]->b_rptr[60]);
printf("%x ", args[1]->b_rptr[61]);
printf("%x ", args[1]->b_rptr[62]);
printf("%x ", args[1]->b_rptr[63]);
printf("%x ", args[1]->b_rptr[64]);
printf("%x ", args[1]->b_rptr[65]);
printf("%x ", args[1]->b_rptr[66]);
printf("\n###########################end#########################\n");
}