Linux高性能服务器编程--第3篇 TCP/IP协议详解 笔记

第16章 服务器调制、调试和测试

16.1 最大文件描述符数

系统分配给应用进程的文件描述符数量是有限制的,所以我们必须关闭那些已经不再使用的文件描述符,以释放它们占用的资源,比如作为守护进程运行的服务器就应该总是关闭标准输入、标准输出、标准错误这3个文件描述符。

Linux对应用进程能打开的最大文件描述符数量有两个层次的限制:用户级限制和系统级限制。用户级限制指目标用户运行的所有进程总共能打开的文件描述符数;系统级限制指所有用户总共能打开的文件描述符数。

​ 以下命令可查看用户级文件描述符限制:

ulimit -n

​ 我们可通过一下方式将用户级文件描述符限制设为max-file-number,以2048为例:

ulimit -SHn 2048

​ 但这种设置是临时的,只在当前session中有效,为永久修改用户级文件描述符数限制,可在/etc/security/limits.conf文件中加入以下两项:

* hard nofile 2048
* soft nofile 2048

​ *第一行是硬限制,第二行是软限制。是通配符,表示所有用户

​ 如果要修改系统级文件描述符数限制,可用以下命令:

sysctl -w fs.file-max=2048

​ 但该命令也是临时更改系统限制,要永久更改系统级文件描述符数限制,需要在/etc/sysctl.conf文件中添加以下项:

fs.file-max=2048

​ 然后执行sysctl -p命令,该Linux系统上的命令用于重新加载系统内核参数设置。

16.2 调整内核参数

​ 几乎所有内核模块,包括内核核心模块和驱动程序,都在/proc/sys目录下提供了某些配置文件以供用户调整模块的属性和行为。通常一个配置文件对应一个内核参数,文件名就是参数的名字,文件内容就是参数的值。我们可通过命令sysctl -a查看所有这些内核参数。

16.2.1 /proc/sys/fs目录下的部分文件

/proc/sys/fs目录下的内核参数都与文件系统相关

对服务器程序来说,有以下重要参数:

  • 1./proc/sys/fs/file-max:系统级文件描述符数限制,修改它是临时性修改,与以上所述临时修改系统级文件描述符数的限制效果相同。一般修改完该文件后,需要把/proc/sys/fs/inode-max设置为新/proc/sys/fs/file-man值的3~4倍,否则可能导致i节点数不够用。
  • 2./proc/sys/fs/epoll/max-user_watches:一个用户能够往epoll内核事件表中注册的事件总量。它是某用户打开的所有epoll实例总共能监听的事件数目,而不是单个epoll实例能监听的事件数目。往epoll内核事件表中注册一个事件,在32位系统上大概消耗90字节的内核空间,在64位系统上则消耗160字节的内核空间。这个内核参数限制了epoll使用的内核内存总量。
16.2.2 /proc/sys/net目录下的部分文件

​ 内核中网络模块的相关参数都位于/proc/sys/net目录下,其中和TCP/IP协议相关的参数主要位于以下3个子目录中:core、ipv4、ipv6,以下是和服务器性能相关的部分参数:

  • 1./proc/sys/net/core/somaxconn:指定listen函数监听队列里,能建立完整连接从而进入ESTABLISHED状态的socket的最大数目。

  • 2./proc/sys/net/ipv4/tcp_max_syn_backlog:指定listen函数监听队列里,能够转移至ESTABLISHED或SYN_RCVD状态的socket的最大数目。

  • 3./proc/sys/net/ipv4/tcp_wmem:它包含3个值,分别指定一个socket的TCP写缓冲区的最小值、默认值、最大值。

  • 4./proc/sys/net/ipv4/tcp_rmem:它包含3个值,分别指定一个socket的TCP读缓冲区的最小值、默认值、最大值。

  • 5./proc/sys/net/ipv4/tcp_syncookies:指定是否打开TCP同步标签(syncookie),同步标签通过启动cookie来防止一个监听socket因不停地重复接收来自同一个地址的连接请求(同步报文段),而导致listen函数监听队列溢出(所谓的SYN风暴)。

​ 除了通过直接修改文件的方式来修改这些系统参数外,我们也可使用sysctl命令来修改它们,这两种修改方式都是临时的,永久的修改方法是在/etc/sysctl.conf文件中加入相应网络参数及其数值,并执行sysctl -p使之生效。

16.3 gdb调试

16.3.1 用gdb调试多进程程序

​ 如果一个进程通过fork系统调用创建了子进程,gdb会继续调试原来的进程,子进程则正常运行,以下方式可调试子进程:

  • 单独调试子进程

​ 子进程本质上来说也是一个进程,因此我们可通过通用的gdb调试方法来调试它,例如,我们可先运行服务器,然后找到子进程的PID,再将其附加(attach)到gdb调试器上,具体操作如下:

在这里插入图片描述

在这里插入图片描述

​ 上图中,b命令表示设置断点,格式为b filename:linenumber;c命令的作用是继续程序的执行,直到遇到下一个断点或程序正常结束;bt命令的作用是打印当前程序的函数调用堆栈(backtrace),显示当前执行路径中各个函数的调用关系和调用帧信息。

  • 使用调试器选项follow-fork-mode

​ gdb调试器的选项follow-fork-mode允许我们选择程序在执行fork系统调用后是继续调试父进程还是调试子进程,其用法如下:

在这里插入图片描述

​ 上图中,mode的可选值是parent和child,分别表示调试父进程和子进程,使用前面的例子,这次使用follow-fork-mode选项来调试子进程,具体过程如下:

在这里插入图片描述

在这里插入图片描述

16.3.2 用gdb调试多线程程序

gdb有一组命令可辅助多线程程序的调试,以下是其中一些常用的命令:

  • 1.info threads:显示当前可调式的所有线程。gdb会为每个线程分配一个ID,我们可使用这个ID来操作对应的线程,ID前面有*的线程是当前被调试的线程。

  • 2.thread ID:调试目标ID指定的线程。

  • 3.set scheduler-locking [off|on|stop]:调试多线程程序时,默认除了被调试的线程在执行外,其他线程也在继续执行,但有时我们仅希望被调试的线程运行,这可通过该命令来实现。该命令设置scheduler-locking的值:off表示不锁定任何线程,即所有线程都可继续执行,这是默认值;on表示只有当前被调试的线程会继续执行;stop表示在单步执行的时候,只有当前线程会执行。

​ 举例,依次调试代码清单中15-6所描述的Web服务器(名为websrv)的父线程和子线程:

在这里插入图片描述

在这里插入图片描述

​ 上图中,gdb的n命令的作用是单步执行程序,一次性执行一行源代码,然后将控制权交还给调试器,以便检查程序状态、变量值和执行路径。

16.4 压力测试

​ 压力测试程序有很多种实现方式,如IO复用方式、多线程(进程)并发编程方式,以及这些方式的结合使用,但单纯的IO复用方式的施压程度是最高的,因为线程和进程的调度本身也要占用一定CPU时间,因此我们使用epoll来实现一个通用的服务器压力测试程序:

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

// 每个客户连接不停向服务器发送这个请求
static const char *request = "GET http://localhost/index.html HTTP/1.1\r\n"
    "Connection: keep-alive\r\n\r\nxxxxxxxxxxxx";

int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epoll_fd, int fd) {
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLOUT | EPOLLET | EPOLLERR;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
}

// 向服务器写入len字节的数据
bool write_nbytes(int sockfd, const char *buffer, int len) {
    int bytes_write = 0;
    printf("write out %d bytes to socket %d\n", len, sockfd);
    while (1) {
        bytes_write = send(sockfd, buffer, len, 0);
        if (bytes_write == -1) {
            return false;
        } else if (bytes_write == 0) {
            return false;
        }
        
        len -= bytes_write;
        buffer = buffer + bytes_write;
        if (len <= 0) {
            return true;
        }
    }
}

// 从服务器读取数据
bool read_once(int sockfd, char *buffer, int len) {
    int bytes_read = 0;
    memset(buffer, '\0', len);
    bytes_read = recv(sockfd, buffer, len, 0);
    if (bytes_read == -1) {
        return false;
    } else if (bytes_read == 0) {
        return false;
    }
    printf("read in %d bytes from socket %d with content: %s\n", bytes_read, sockfd, buffer);
    
    return true;
}

// 向服务器发起num参数个TCP连接,我们可以通过改变num参数来调整测试压力
void start_conn(int epoll_fd, int num, const char *ip, int port) {
    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    
    for (int i = 0; i < num, ++i) {
        sleep(1);
        int sockfd = socket(PF_INET, SOCK_STREAM, 0);
        printf("create 1 sock\n");
        if (sockfd < 0) {
            continue;
        }
        
        if (connect(sockfd, (struct sockaddr *)&address, sizeof(address)) == 0) {
            printf("build connection %d\n", i);
            addfd(epoll_fd, sockfd);
        }
    }
}

void close_conn(int epoll_fd, int sockfd) {
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sockfd, 0);
    close(sockfd);
}

int main(int argc, char *argv[]) {
    assert(argc == 4);
    int epoll_fd = epoll_create(100);
    start_conn(epoll_fd, atoi(argv[3]), argv[1], atoi(argv[2]));
    epoll_event events[10000];
    char buffer[2048];
    while (1) {
        // 第3个参数是第2个参数数组的大小
        // 第4个参数是等待的毫秒数
        int fds = epoll_wait(epoll_fd, events, 10000, 2000);
        for (int i = 0; i < fds; ++i) {
            int sockfd = events[i].data.fd;
            if (events[i].events & EPOLLIN) {
                // 此处代码有问题,当读取失败后(对端关闭连接或读函数失败)
                // 先关闭了连接,然后接着又监听被关闭连接的可写状态,此处关闭连接后,应该continue
                if (!read_once(sockfd, buffer, 2048)) {
                    close_conn(epoll_fd, sockfd);
                }
                struct epoll_event event;
                event.events = EPOLLOUT | EPOLLET | EPOLLERR;
                event.data.fd = sockfd;
                epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sockfd, &event);
            } else if (events[i].events & EPOLLOUT) {
                // 此处也是同样的问题
                if (!write_nbytes(sockfd, request, strlen(request))) {
                    close_conn(epoll_fd, sockfd);
                }
                struct epoll_event event;
                event.events = EPOLLIN | EPOLLET | EPOLLERR;
                event.data.fd = sockfd;
                epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sockfd, &event);
            } else if (events[i].events & EPOLLERR) {
                close_conn(epoll_fd, sockfd);
            }
        }
    }
}

​ 使用以上压力测试程序(名为stress_test)来测试第十五章中用线程池实现的Web服务器,我们现在测试机器ernest-laptop上运行websrv,然后从Kongming20上执行stress_test,向websrv服务器发起1000个连接,具体操作如下:

在这里插入图片描述

​ 如果websrv服务器程序足够稳定,那么websrv和stress_test这两个进程将一直运行下去,并不断交换数据。

第17章 系统监测工具

17.1 tcpdump

​ tcpdump是一款经典的抓包工具

​ tcpdump提供了一些选项用以过滤数据包或定制输出格式,常见的选项如下:

  • 1.-n:使用IP地址表示主机,而非主机名;使用数字表示端口号,而非服务名。

  • 2.-i:指定要监听的网卡接口。-i any表示抓取所有网卡接口上的数据包。

  • 3.-v:输出更详细的信息,如显式IP数据包中的TTL和TOS信息。

  • 4.-t:不打印时间戳。

  • 5.-e:显示以太网帧头部信息。

  • 6.-c:仅抓取指定数量的数据包。

  • 7.-x:以十六进制数显示数据包的内容,但不显示包中以太网帧的头部信息。

  • 8.-X:与7类似,但还打印每个十六进制字节对应的ASCII字符。

  • 9.-XX:与8类型,但还打印以太网帧的头部信息。

  • 10.-s:设置抓包时的抓取长度,当数据包的长度超过抓取长度时,tcpdump抓取到的是被截断的数据包。在4.0及之前的版本中,默认的抓包长度是68字节,这对于IP、TCP、UDP等协议时足够的,但对于DNS、NFS等协议,68字节通常不能容纳一个完整的数据包。4.0版本后,默认的抓包长度被修改为65535字节。

  • 11.-S:以绝对值来显示TCP报文段的需要,而非相对值。

  • 12.-w:将tcpdump的输出以某种格式定向到某个文件。

  • 13.-r:从文件读取数据包信息并显示之。

​ 除了使用选项外,tcpdump还支持用表达式进一步过滤数据包,tcpdump表示式的操作数分为3种:

  • 1.类型:解释其后面跟着的参数的含义,其可选值包括host、net、port、portrange,它们分别指定主机名(或IP地址)、用CIDR方法表示的网络地址、端口号、端口范围。如要抓取整个1.2.3.0/255.255.255.0网络上的数据包,可用以下命令:
  • 在这里插入图片描述
  • 2.方向:其可选值为src(指定数据包的发送端)、dst(指定数据包的目的端)。如要抓取进入端口13579的数据包,可用以下命令:
  • 在这里插入图片描述
  • 3.协议:指定目标协议,如要抓取所有ICMP数据包,可用以下命令:
  • 在这里插入图片描述

​ 我们还可使用逻辑操作符来组织上述操作数以创建更复杂的表达式,tcpdump支持的逻辑操作符和编程语言中的逻辑操作符完全相同,包括and(&&)、or(||)、not(!)。

​ 如要抓取主机ernest-laptop和所有非Kongming20的主机之间交换的IP数据包,可用以下命令:

在这里插入图片描述

​ 如果表达式比较复杂,我们可以用括号将它们分组,但在使用括号时,我们要么用反斜杠\对它转义,要么使用单引号’将其括住,以避免它被shell所解释。如要抓取来自主机10.0.2.4,目标端口是3389或22的数据包,可使用如下命令:

在这里插入图片描述

​ tcpdump还允许直接使用数据包中的部分协议字段的内容来过滤数据包,如仅抓取TCP同步报文段,可使用如下命令:

在这里插入图片描述

​ 这是因为TCP头部的第14个字节的第2个位是同步标志,该命令也可表示为:

在这里插入图片描述

17.2 lsof

​ lsof(list open file)命令是一个列出当前系统打开的文件描述符的工具,通过它我们可以了解感兴趣的进程打开了哪些文件描述符,或者我们感兴趣的文件描述符被哪些进程打开了。

lsof命令常用的选项:

  • 1.-i:显示socket文件描述符,该选项的使用方法是:

  • 在这里插入图片描述

  • ​ 其中,4表示IPv4协议,6表示IPv6协议;protocol指定传输层协议,可以是TCP或UDP;hostname指定主机名;ipaddr指定主机的IP地址;service指定服务名;port指定端口号。如要显示所有连接到主机192.168.1.108的ssh服务的socket文件描述符,可用以下命令:

  • 在这里插入图片描述

  • ​ 如果-i后不跟任何参数,则lsof命令将显示所有socket文件描述符。

  • 2.-u:显示指定用户启动的所有进程打开的所有文件描述符。

  • 3.-c:显示指定的命令打开的所有文件描述符,比如要查看websrv程序打开了哪些文件描述符,可用以下命令:

  • 4.-p:显示指定进程打开的所有文件描述符。

  • 5.-t:仅显示打开了目标文件描述符的进程的PID。

我们还可直接将文件名作为lsof命令的参数,以查看哪些进程打开了该文件。

17.3 nc

nc(netcat)命令主要用来快速构建网络连接,我们可以让它以服务器方式运行,监听某个端口并接收客户连接,因此它可用来调试客户端程序,我们也可以使之以客户端方式运行,向服务器发起连接并收发数据,因此它可用来调试服务器程序,此时它有点像telnet程序。

nc命令常用的选项:

  • 1.-i:设置数据包传送的时间间隔。

  • 2.-l:以服务器方式运行,监听指定的端口。nc命令默认以客户端方式运行。

  • 3.-k:保持服务器持续监听,nc不会在客户端连接终止后退出,而是继续保持监听状态,必须与-l选项一起使用。

  • 4.-n:使用IP地址表示主机,而不是主机名;使用数字表示端口号,而不是服务名。即不做任何DNS或服务查找,此时用户需要提供原始IP地址和端口号,使用该选项的情形:

    • (1)需要快速测试,避免额外的解析带来的时间消耗。

    • (2)DNS解析不可用时,可以使用IP进行连接。

    • (3)需要指定特定的地址,避免解析结果带来的不确定性。

  • 5.-p:当nc命令以客户端运行时,强制其使用指定的端口号。

  • 6.-s:设置本地主机发送出的数据包的IP地址。

  • 7.-C:将CR和LF两个字符作为行结束符。

  • 8.-U:使用UNIX本地域协议通信。

  • 9.-u:使用UDP协议。nc命令默认使用的传输层协议是TCP协议。

  • 10.-w:如果nc客户端在指定的时间内未检测到任何输入,则退出。

  • 11.-X:当nc客户端和代理服务器通信时,该选项指定它们之间使用的通信协议,目前nc支持的代理协议有4(SOCKS v.4)、5(SOCKS v.5)、connect(HTTPS proxy),nc默认使用的代理协议是SOCKS v.5。

  • 12.-x:指定目标代理服务器的IP地址和端口号,比如,要连接到ernest-laptop上的squid代理服务器,并通过它来访问www.baidu.com的Web服务,可用以下命令:

  • 在这里插入图片描述

  • 13:-z:扫描目标机器上的某个或某些服务是否开启(端口扫描),比如,要扫描机器ernest-laptop上端口在20~50之间的服务,可用以下命令:

  • 在这里插入图片描述

​ 我们可用以下方式来连接websrv服务器并向它发送数据:

在这里插入图片描述

​ 上图中使用了-C选项,这样我们每次按下回车键向服务器发送一行数据时,nc客户端都会给服务器额外发送一个<CR><LF>,这正是websrv服务器期望的HTTP行结束符。发完第三行数据后,我们得到了服务器的响应,内容正是我们期望的:服务器没有找到被请求的资源文件a.html。

17.4 strace

strace跟踪程序运行过程中指定的系统调用和接收到的信号,并将系统调用名、参数、返回值、信号名输出到标准输出或指定文件。

strace命令常用选项包括:

  • 1.-c:统计每个系统调用执行时间、执行次数、出错次数。

  • 2.-f:跟踪由fork函数生成的子进程。

  • 3.-t:在输出的每行信息前加上时间信息。

  • 4.-e:指定一个表达式,用来控制如何跟踪系统调用(或接收到的信号,下同),其格式为:

    • 在这里插入图片描述
    • qualifier可以是trace、abbrev、verbose、raw、signal、read、write之一,默认是trace。value是用于进一步限制被跟踪的系统调用的符号或数值,它的两个特殊取值时all和none,分别表示跟踪所有由qulifier指定类型的系统调用和不跟踪任何该类型的系统调用。以下是其他value取值:
      • (1)-e trace=set:只跟踪指定的系统调用。例如-e trace=open, close, read, write表示只跟踪open、close、read、write这4种系统调用。

      • (2)-e trace=file:只跟踪与文件操作相关的系统调用。

      • (3)-e trace=process:只跟踪与进程控制相关的系统调用。

      • (4)-e trace=network:只跟踪与网络相关的系统调用。

      • (5)-e trace=signal:只跟踪与信号相关的系统调用。

      • (6)-e trace=ipc:只跟踪与进程间通信相关的系统调用。

      • (7)-e signal=set:只跟踪指定信号,如-e signal=!SIGIO表示跟踪除SIGIO之外的所有信号。

      • (8)-e read=set:输出从指定文件中读入的数据,如-e read=3, 5表示输出所有从文件描述符3和5读入的数据。

  • 5.-o:将strace的输出写入指定的文件。

    • strace命令的每行都包含以下字段:系统调用名、参数、返回值,如下面的实例:

    • 在这里插入图片描述

    • 这行输出表示,程序cat /dev/null在运行过程中执行了open系统调用,open调用以只读方式打开了大文件/dev/null,然后返回了值为3的文件描述符。上图中命令会输出很多内容,我们省略了次要的信息,后面的示例中,我们也仅显示主题相关的内容。

    • 当系统调用发生错误时,strace命令将输出错误标识和描述,如以下示例:

    • 在这里插入图片描述

    • 上图中的O_LARGEFILE并不是POSIX标准的一部分,而是一些UNIX-like操作系统的扩展,它通常用于兼容早期UNIX系统,告诉操作系统要支持大文件(大于2 GB)的访问。

17.5 netstat

netstat可以打印本地网卡接口上的全部连接、路由表信息、网卡接口信息等,本书主要使用上述功能中的第一个,即显示TCP连接及其状态信息,毕竟,要想获得路由表信息和网卡接口信息,我们可以使用输出内容更丰富的route(管理和显示系统的网络路由表的命令)和ifconfig命令。

​ netstat命令常用选项:

  • 1.-n:使用IP地址表示主机,而不是主机名;使用数字表示端口号,而不是服务名。

  • 2.-a:也显示监听socket。

  • 3.-t:仅显示TCP连接。

  • 4.-r:显示路由信息。

  • 5.-i:显示网卡接口的数据流量。

  • 6.-c:每隔1秒输出一次。

  • 7.-o:显示socket定时器(如保活定时器)的信息。

  • 8.-p:显示socket所属的进程的PID和名字。

​ 运行websrv服务器,并执行telnet命令对它发起一个连接请求:

在这里插入图片描述

​ 然后执行netstat -nat | grep 127.0.0.1:13579来查看连接状态,结果如下:

在这里插入图片描述

​ 上图的输出中,第1行表示本地socket地址127.0.0.1:13579处于LISTEN状态,并等待任何远端socket(用0.0.0.0:*表示)对它发起连接。第2行表示服务器和远端地址127.0.0.1:48220建立了一个连接。第3行是从客户端角度重复输出第2行信息表示的这个连接,因为我们是在同一台机器上运行服务器和客户端的。

17.6 vmstat

vmstat(virtual memory statistics)命令能实时输出系统的各种资源的使用情况,如进程信息、内存使用、CPU使用率、IO使用情况。

​ vmstat命令常用选项:

  • 1.-f:显示系统自启动以来执行的fork次数。

  • 2.-s:显示内存相关的统计信息及多种系统活动的数量(如CPU上下文切换次数)。

  • 3.-d:显示磁盘相关的统计信息。

  • 4.-p:显示磁盘分区的统计信息。

  • 5.-S:使用指定单位来显示。参数k、K、m、M分别代表1000、1024、1000000、1048576字节。

  • 6.delay:采样间隔(单位是秒),即每隔delay的时间输出一次统计信息。

  • 7.count:采样次数,输出count次统计信息。

​ 默认vmstat命令的输出:

在这里插入图片描述

​ 第1行输出是自系统启动以来的平均结果,而后面的输出是采样间隔内的平均结果。

17.7 ifstat

ifstat(interface statistics)命令可用于监测网络流量,其常用选项如下:

  • 1.-a:监测系统上所有网卡接口。

  • 2.-i:指定要监测的网卡接口。

  • 3.-t:在每行输出信息前加上时间戳。

  • 4.-b:以Kbit/s为单位显示数据,而不是默认的KB/s。

  • 5.delay:采样间隔,单位是秒,即每隔delay时间输出一次统计信息。

  • 6.count:采样次数,即总共输出count次统计信息。

​ ifstat命令的例子:

在这里插入图片描述

​ 从输出来看,该主机拥有两个网卡接口:虚拟的回路接口lo和以太网网卡接口eth0。使用ifstat命令可以大概估计各个时段服务器的总输入、输出流量。

17.8 mpstat

mpstat(multi-processor statistics)命令能实时监测多处理器系统上每个CPU的使用情况。mpstat命令和iostat命令通常都集成在包sysstat中,安装sysstat即可获得这两个命令。mpstat命令的典型用法如下:

在这里插入图片描述

​ 选项P指定要监听的CPU号(0~CPU个数-1),其值ALL表示监听所有CPU。interval参数是采样间隔,单位是秒,即每个interval的时间输出一次统计信息。count参数是采样次数,即共输出count次统计信息,但mpstat命令最后还会输出这count次采样结果的平均值。与vmstat命令一样,mpstat命令输出的第一次结果是自系统启动以来的平均结果,而后面的输出结果则是采样间隔内的平均结果。

​ 使用mpstat命令的例子:

在这里插入图片描述

​ 为显示方便,我们省略了每行输出的前导时间戳。每次采样的输出都包含3条信息,每条信息包含以下几个字段:

  • 1.CPU:指示该条信息是哪个CPU的数据。0表示第1个CPU的数据,1表示第2个CPU的数据,all是这两个CPU数据的平均值。

  • 2.%usr:除了nice值为负的进程,系统上其他进程运行在用户空间的时间占CPU总运行时间的比例。

  • 3.%nice:nice值为负的进程运行在用户空间的时间占CPU总运行时间的比例。

  • 4.%sys:系统上所有进程运行在内核空间的时间占CPU总运行时间的比例,但不包括硬件和软件中断消耗的CPU时间。

  • 5.%iowait:CPU等待磁盘操作的时间占CPU总运行时间的比例。

  • 6.%irq:CPU用于处理硬件中断的时间占CPU总运行时间的比例。

  • 7.%soft:CPU用于处理软件中断的时间占CPU总运行时间的比例。

  • 8.%steal:一个物理CPU可以包含一对虚拟CPU,这一对虚拟CPU由超级管理程序管理。当超级管理程序在处理某个虚拟CPU时,另外一个虚拟CPU必须等待它处理完成才能运行,这部分等待事件就是steal时间,该字段表示steal时间占CPU总运行时间的比例。

  • 9.%guest:运行虚拟CPU的时间占总运行时间的比例。

  • 10.%idle:系统空闲的时间占CPU总运行时间的比例。

​ 在以上所有输出字段中,我们最关心的是%user、%sys、%idle,它们基本反映了我们的代码中业务逻辑代码和系统调用所占的比例,以及系统还能承受多大的负载。上图输出中,执行系统调用占用的CPU时间比执行用户业务逻辑占用的CPU时间要多,这是因为我们运行了第十六章中的压力测试工具,它在不停地执行recv/send系统调用来收发数据。

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值