ksar、sar及相关内核知识点解析【转】


转自:

关键词:sar、sadc、ksar、/proc/stat、/proc/cpuinfo、/proc/meminfo、/proc/diskstats。

在之前有简单介绍过sar/ksar,最近在使用中感觉需要再深入了解一下。

ksar/sar从内核采集数据,并输出可读性数据。分析相关源码,有助于知道数据来龙去脉。--------------------------------------1. sar源码概览

ksar显示sar/sadc获取的数据,并图形化显示。数据从内核节点,到sadc/sar转换,再到ksar显示。----------------------------2. ksar处理流程

对照ksar每张图表,然后sar/sadc对应的采集转换,再到内核每个数据项含义解析。--------------------------------------------3. ksar解读

期望是从ksar上的图表能对应到内核的代码,明白这些图表数据根源。

rw_sa_stat_loop(count, stdfd, ofd, ofile, sa_dir); #ifdef HAVE_SENSORS /* Cleanup sensors */ sensors_cleanup(); #endif /* HAVE_SENSORS */ /* Free structures */ sa_sys_free(); return 0; }

rw_sa_stat_loop()是整个sadc的核心循环,这里从个sysfs读取信息,并提取关键信息,然后保存。

act[]。

void read_stats(void)
{
    int i;
    __nr_t cpu_nr = act[get_activity_position(act, A_CPU, EXIT_IF_NOT_FOUND)]->nr;

    record_hdr.uptime0 = 0;
    if (cpu_nr > 2) {
        read_uptime(&(record_hdr.uptime0));
    }

    for (i = 0; i < NR_ACT; i++) {
        if (IS_COLLECTED(act[i]->options)) {---------------------------------------遍历所有act[],如果act[]中对应的options包含AO_COLLECTED则进行f_read()。
            /* Read statistics for current activity */            (*act[i]->f_read)(act[i]);
        }
    }

    if (cpu_nr == 1) {
        record_hdr.uptime0 = record_hdr.uptime;
    }
}

act[]存放了所有统计事件的struct activity。

cpu_act,
    &pcsw_act,
    &irq_act,
    &swap_act,
    &paging_act,
    &io_act,
    &memory_act,
    &huge_act,
    &ktables_act,
    &queue_act,
    &serial_act,
    &disk_act,
    /*  */
    &net_dev_act,
    &net_edev_act,
    &net_nfs_act,
    &net_nfsd_act,
    &net_sock_act,
    &net_ip_act,
    &net_eip_act,
    &net_icmp_act,
    &net_eicmp_act,
    &net_tcp_act,
    &net_etcp_act,
    &net_udp_act,
    &net_sock6_act,
    &net_ip6_act,
    &net_eip6_act,
    &net_icmp6_act,
    &net_eicmp6_act,
    &net_udp6_act,
    &fchost_act,
    &softnet_act,    /* AO_CLOSE_MARKUP */
    /*  */
    /*  */
    &pwr_cpufreq_act,
    &pwr_fan_act,
    &pwr_temp_act,
    &pwr_in_act,
    &pwr_wghfreq_act,
    &pwr_usb_act,        /* AO_CLOSE_MARKUP */
    /*  */
    &filesystem_act
};

1.2 sar显示统计信息

read_sadc_stat_bunch()读取统计信息,write_stats()调用每个struct activity的f_print()函数,write_stats_avg()调用每个struct activity的f_print_avg()函数。

f_print()和f_print_avg()或从文件中解析字符串,或启动sadc采样,然后再解析。

ksar安装文件。

ksar中看到的图标是结果,这些数据是通过sadc采集,sar解析出来的。

sadc是通过读取sysfs/procfs节点来获取信息,这些节点都是内核提供的统计信息。

sar -o temp.bin 1 600--------------------------------------------------sar将采样数据保存在temp.bin中。

LC_ALL=C sar -A -f temp.bin > sar.txt----------------------------将保存的采样数据temp.bin,转换成更可读性强的文本文件。

所以ksar的每一张图标,都对应了内核统计信息。

sar是数据搬运整理工具,ksar是图形化工具。

下面对每张图标从ksar,到sar/sadc,最终到内核中每个数据。

2.2 ksar操作

2.2.1 数据导入

通过Data->Append from a file...从txt中加载数据,还有其他两种数据来源方式。

2.2.2 数据导出

如果要将图表导出,可以在每张图标下面选择Export PNG之类。

或者通过Export->Export to PDF...,选择指定选项。

3. ksar解析

下面结合ksar图表来逐项解析。

/proc/stat节点,解析其中cpu信息,包括合计cpu信息以及单个cpu信息。

/proc/stat中的信息包括一个合计以及多个cpu单独统计信息。

从/proc/stat中读取的信息都是从启动以来的累计时间,在图标中显示的是一个时间段的差值。

然后计算不同模块耗时占比。

void read_stat_cpu(struct stats_cpu *st_cpu, int nbr,
           unsigned long long *uptime, unsigned long long *uptime0)
{
    FILE *fp;
    struct stats_cpu *st_cpu_i;
    struct stats_cpu sc;
    char line[8192];
    int proc_nb;

    if ((fp = fopen(STAT, "r")) == NULL) {----------------------------------------------------/proc/stat
        fprintf(stderr, _("Cannot open %s: %s\n"), STAT, strerror(errno));
        exit(2);
    }

    while (fgets(line, sizeof(line), fp) != NULL) {

        if (!strncmp(line, "cpu ", 4)) {------------------------------------------------------统计总cpu不同类别耗时,详细信息参考/proc/stat。这里的信息和内核中一一对应。
            memset(st_cpu, 0, STATS_CPU_SIZE);
            sscanf(line + 5, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
                   &st_cpu->cpu_user,
                   &st_cpu->cpu_nice,
                   &st_cpu->cpu_sys,
                   &st_cpu->cpu_idle,
                   &st_cpu->cpu_iowait,
                   &st_cpu->cpu_hardirq,
                   &st_cpu->cpu_softirq,
                   &st_cpu->cpu_steal,
                   &st_cpu->cpu_guest,
                   &st_cpu->cpu_guest_nice);

            *uptime = st_cpu->cpu_user + st_cpu->cpu_nice    +
                st_cpu->cpu_sys    + st_cpu->cpu_idle    +
                st_cpu->cpu_iowait + st_cpu->cpu_hardirq +
                st_cpu->cpu_steal  + st_cpu->cpu_softirq;
        }

        else if (!strncmp(line, "cpu", 3)) {
...
    }

    fclose(fp);
}

/proc/stat节点,解析其中的ctxt和processes信息。

同样的/proc/stat中,统计信息是启动以来的累计值,图标中现实的不同时间段的差值。

proc/s表示每秒创建的进程数目,cswch/s表示每秒进程切换次数。

void read_stat_pcsw(struct stats_pcsw *st_pcsw)
{
    FILE *fp;
    char line[8192];

    if ((fp = fopen(STAT, "r")) == NULL)
        return;

    while (fgets(line, sizeof(line), fp) != NULL) {

        if (!strncmp(line, "ctxt ", 5)) {--------------------------------------------ctxt是所有CPU的进程切换次数。
            /* Read number of context switches */
            sscanf(line + 5, "%llu", &st_pcsw->context_switch);
        }

        else if (!strncmp(line, "processes ", 10)) {---------------------------------是整个系统创建进程的次数,total_forks,
            /* Read number of processes created since system boot */
            sscanf(line + 10, "%lu", &st_pcsw->processes);
        }
    }

    fclose(fp);
}

/proc/vmstat节点,解析其中的pswpin和pswpout两个信息。

pswpin/s表示系统每秒从swap分区读入的页面数量,即移除掉swap;pswpout/s表示系统每秒写到swap分区的页面数量,即产生swap。

void read_vmstat_swap(struct stats_swap *st_swap)
{
    FILE *fp;
    char line[128];

    if ((fp = fopen(VMSTAT, "r")) == NULL)
        return;

    while (fgets(line, sizeof(line), fp) != NULL) {

        if (!strncmp(line, "pswpin ", 7)) {-------------------------------对应PSWPIN,表示从swap分区读入页面。
            /* Read number of swap pages brought in */
            sscanf(line + 7, "%lu", &st_swap->pswpin);
        }
        else if (!strncmp(line, "pswpout ", 8)) {-------------------------对应PSWPOUT,表示将page写入到swap分区。
            /* Read number of swap pages brought out */
            sscanf(line + 8, "%lu", &st_swap->pswpout);
        }
    }

    fclose(fp);
}

diskstats_show()函数。

void read_diskstats_io(struct stats_io *st_io)
{
    FILE *fp;
    char line[256];
    char dev_name[MAX_NAME_LEN];
    unsigned int major, minor;
    unsigned long rd_ios, wr_ios, rd_sec, wr_sec;

    if ((fp = fopen(DISKSTATS, "r")) == NULL)
        return;

    while (fgets(line, sizeof(line), fp) != NULL) {

        if (sscanf(line, "%u %u %s %lu %*u %lu %*u %lu %*u %lu",-----------scanf的%u之间的星号,表示跳过此输入。
               &major, &minor, dev_name,
               &rd_ios, &rd_sec, &wr_ios, &wr_sec) == 7) {

            if (is_device(dev_name, IGNORE_VIRTUAL_DEVICES)) {--------------虚拟设备没有/sys/block//device,此特性用以判断dev_name对应的设备是否是真实的块设备。
                st_io->dk_drive      += (unsigned long long) rd_ios + (unsigned long long) wr_ios;
                st_io->dk_drive_rio  += rd_ios;
                st_io->dk_drive_rblk += rd_sec;
                st_io->dk_drive_wio  += wr_ios;
                st_io->dk_drive_wblk += wr_sec;
            }
        }
    }
    fclose(fp);
}

/proc/diskstats来源于内核的diskstats_show()

/proc/meminfo的内存和swap信息。

kbmemfree是所有free内存大小,kbavail是扣除保留内存,加上部分pagecache和可回收内存;kbmemused是目前系统内存使用量,不包括内核使用量。

%memused是内存使用占总内存百分比。

kbcommit是当前场景系统需要使用到的内存量,实际上可能并没有申请这么多内存。这是因为分配的内存只有在使用到时,才会产生缺页异常。

这里的kbcommit和页面统计信息相呼应,此处kbcommit突然增加,产生了很多page fault。

从图表中的名称大概就能找到对应的/proc/meminfo中的字符项。

其中%memused和%commit查看stub_print_memory_stats(),%memused为(MemTotal-MemFree)/MemTotal。

%commit为Commited_AS/(MemTotal+SwapTotal)。

在/proc/meminfo中看到的CommitLimit可能是proc/sys/vm/overcommit_kbytes;或者按照当前可以用内存乘以overcommit_ratio这个比例得到。

在实际使用中,还需要结合overcommit_memory类型来看,参考overcommit_memory和overcommit_ratio。

unsigned long vm_commit_limit(void)
{
    unsigned long allowed;

    if (sysctl_overcommit_kbytes)
        allowed = sysctl_overcommit_kbytes >> (PAGE_SHIFT - 10);
    else
        allowed = ((totalram_pages - hugetlb_total_pages())
               * sysctl_overcommit_ratio / 100);
    allowed += total_swap_pages;

    return allowed;
}

read_meminfo()从/proc/meminfo中解析数据。

void read_meminfo(struct stats_memory *st_memory)
{
    FILE *fp;
    char line[128];

    if ((fp = fopen(MEMINFO, "r")) == NULL)
        return;

    while (fgets(line, sizeof(line), fp) != NULL) {

        if (!strncmp(line, "MemTotal:", 9)) {
            /* Read the total amount of memory in kB */
            sscanf(line + 9, "%lu", &st_memory->tlmkb);
        }
        else if (!strncmp(line, "MemFree:", 8)) {
            /* Read the amount of free memory in kB */
            sscanf(line + 8, "%lu", &st_memory->frmkb);
        }
        else if (!strncmp(line, "MemAvailable:", 13)) {
            /* Read the amount of available memory in kB */
            sscanf(line + 13, "%lu", &st_memory->availablekb);
        }
        else if (!strncmp(line, "Buffers:", 8)) {
            /* Read the amount of buffered memory in kB */
            sscanf(line + 8, "%lu", &st_memory->bufkb);
        }
        else if (!strncmp(line, "Cached:", 7)) {
            /* Read the amount of cached memory in kB */
            sscanf(line + 7, "%lu", &st_memory->camkb);
        }
        else if (!strncmp(line, "SwapCached:", 11)) {
            /* Read the amount of cached swap in kB */
            sscanf(line + 11, "%lu", &st_memory->caskb);
        }
        else if (!strncmp(line, "Active:", 7)) {
            /* Read the amount of active memory in kB */
            sscanf(line + 7, "%lu", &st_memory->activekb);
        }
        else if (!strncmp(line, "Inactive:", 9)) {
            /* Read the amount of inactive memory in kB */
            sscanf(line + 9, "%lu", &st_memory->inactkb);
        }
        else if (!strncmp(line, "SwapTotal:", 10)) {
            /* Read the total amount of swap memory in kB */
            sscanf(line + 10, "%lu", &st_memory->tlskb);
        }
        else if (!strncmp(line, "SwapFree:", 9)) {
            /* Read the amount of free swap memory in kB */
            sscanf(line + 9, "%lu", &st_memory->frskb);
        }
        else if (!strncmp(line, "Dirty:", 6)) {
            /* Read the amount of dirty memory in kB */
            sscanf(line + 6, "%lu", &st_memory->dirtykb);
        }
        else if (!strncmp(line, "Committed_AS:", 13)) {
            /* Read the amount of commited memory in kB */
            sscanf(line + 13, "%lu", &st_memory->comkb);
        }
        else if (!strncmp(line, "AnonPages:", 10)) {
            /* Read the amount of pages mapped into userspace page tables in kB */
            sscanf(line + 10, "%lu", &st_memory->anonpgkb);
        }
        else if (!strncmp(line, "Slab:", 5)) {
            /* Read the amount of in-kernel data structures cache in kB */
            sscanf(line + 5, "%lu", &st_memory->slabkb);
        }
        else if (!strncmp(line, "KernelStack:", 12)) {
            /* Read the kernel stack utilization in kB */
            sscanf(line + 12, "%lu", &st_memory->kstackkb);
        }
        else if (!strncmp(line, "PageTables:", 11)) {
            /* Read the amount of memory dedicated to the lowest level of page tables in kB */
            sscanf(line + 11, "%lu", &st_memory->pgtblkb);
        }
        else if (!strncmp(line, "VmallocUsed:", 12)) {
            /* Read the amount of vmalloc area which is used in kB */
            sscanf(line + 12, "%lu", &st_memory->vmusedkb);
        }
    }

    fclose(fp);
}

下图的kbswpfree、kbswpused、kbswpcad通过字面意思即可知其对应的meminfo项为SwapFree、SwapTotal-SwapFree、SwapCached。

rtnl_link_stats64 *stats = dev_get_stats(dev, &temp); seq_printf(seq, "%6s: %7llu %7llu %4llu %4llu %4llu %5llu %10llu %9llu " "%8llu %7llu %4llu %4llu %4llu %5llu %7llu %10llu\n", dev->name, stats->rx_bytes, stats->rx_packets, stats->rx_errors, stats->rx_dropped + stats->rx_missed_errors, stats->rx_fifo_errors, stats->rx_length_errors + stats->rx_over_errors + stats->rx_crc_errors + stats->rx_frame_errors, stats->rx_compressed, stats->multicast, stats->tx_bytes, stats->tx_packets, stats->tx_errors, stats->tx_dropped, stats->tx_fifo_errors, stats->collisions, stats->tx_carrier_errors + stats->tx_aborted_errors + stats->tx_window_errors + stats->tx_heartbeat_errors, stats->tx_compressed); }

rtnl_link_stats64。

void read_net_edev(struct stats_net_edev *st_net_edev, int nbr)
{
    FILE *fp;
    struct stats_net_edev *st_net_edev_i;
    static char line[256];
    char iface[MAX_IFACE_LEN];
    int dev = 0;
    int pos;

    if ((fp = fopen(NET_DEV, "r")) == NULL)
        return;

    while ((fgets(line, sizeof(line), fp) != NULL) && (dev < nbr)) {

        pos = strcspn(line, ":");
        if (pos < strlen(line)) {
            st_net_edev_i = st_net_edev + dev;
            strncpy(iface, line, MINIMUM(pos, MAX_IFACE_LEN - 1));
            iface[MINIMUM(pos, MAX_IFACE_LEN - 1)] = '\0';
            sscanf(iface, "%s", st_net_edev_i->interface); /* Skip heading spaces */
            sscanf(line + pos + 1, "%*u %*u %llu %llu %llu %llu %*u %*u %*u %*u "
                   "%llu %llu %llu %llu %llu",
                   &st_net_edev_i->rx_errors,------------------对应rx_erros,表示接受到的bad packet。
                   &st_net_edev_i->rx_dropped,-----------------对应rx_dropped+rx_missed_errors。
                   &st_net_edev_i->rx_fifo_errors,-------------对应rx_fifo_errors。
                   &st_net_edev_i->rx_frame_errors,------------对应rx_length_errors+rx_over_errros。
                   &st_net_edev_i->tx_errors,
                   &st_net_edev_i->tx_dropped,
                   &st_net_edev_i->tx_fifo_errors,
                   &st_net_edev_i->collisions,-----------------对应collisions
                   &st_net_edev_i->tx_carrier_errors);---------包括tx_carrier_errors+tx_aborted_errors+tx_window_errors+tx_heartbeat_errors。
            dev++;
        }
    }

    fclose(fp);
}

获取网络设备统计信息的核心数据结构式struct rtnl_link_stat64。

sockstat_seq_show(),这里面统计了当前系统socket的使用情况。

read_net_sock()函数解析。

可见totsck表示系统所有Socket数目,其他子类表示不同类型socket数目。

softnet_seq_show()。

static int softnet_seq_show(struct seq_file *seq, void *v)
{
    struct softnet_data *sd = v;
    unsigned int flow_limit_count = 0;

#ifdef CONFIG_NET_FLOW_LIMIT
    struct sd_flow_limit *fl;

    rcu_read_lock();
    fl = rcu_dereference(sd->flow_limit);
    if (fl)
        flow_limit_count = fl->count;
    rcu_read_unlock();
#endif

    seq_printf(seq,
           "%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
           sd->processed, sd->dropped, sd->time_squeeze, 0,
           0, 0, 0, 0, /* was fastroute */
           0,    /* was cpu_collision */
           sd->received_rps, flow_limit_count);
    return 0;
}
联系方式:arnoldlu@qq.com

相关