FreeBSD: загрузка процессора

6th Апрель 2012 | Метки: , , , , ,
Опубликовал: Dmitriy [dk] Kalinin

Все, кто знаком с *NIX-подобными системами знает про прекрасную команду «top», которая показывает текущее состояние системы: загруженность процессора, распределение оперативной памяти, нагрузку, процессы и так далее.

В этой статье хотелось бы сделать обзор загруженности процесса на ОС FreeBSD, а именно — какие этапы проходят от набора команды «top -P» (вывод нагрузки по процессорам) до вывода данных на экран.

Как видно на скриншоте — есть информация по 4 процессорам (или ядрам), с разбивкой по нагрузке: user, nice, system, interrupt, idle. Попробуем получить эти данные из ядра самостоятельно.

Для доступа к данным ядра в FreeBSD используется библиотека libkvm, от нее и будем отталкиваться. Данные ядра доступны через устройство «/dev/mem», поэтому к нему во время экспериментов потребуется доступ на чтении. Можно добавить себя в группу kmem, или экспериментировать от суперпользователя (root).

Прежде, чем определять загруженность процессоров — надо определить, а сколько их вообще в системе? Для этого существует функция:

int kvm_getmaxcpu(kvm_t *kd);

Функция вернет количество процессоров, поддерживаемое системой, или «-1» если случилась какая-либо ошибка. Количество поддерживаемых процессоров не равняется количество установленных процессоров.

Создаем шаблон для работы с ядром:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/pcpu.h>
#include <kvm.h>

int main()
{
  kvm_t *kd;
  // kvm descriptor
  kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm");
  if (kd)
  {
    // ...
    kvm_close(kd);
  }
}

Первым делаем запрашиваем у системы — сколько процессоров поддерживается:

    // Запрашиваем количество поддерживаемых системой процессоров
    maxcpu = kvm_getmaxcpu(kd);
    printf("maxcpu=%d\n", maxcpu);

Дальше получаем информация по процессорам:

      int n;
      struct pcpu **cpu;
      // Запрашиваем данные по процессорам
      cpu = calloc(sizeof(struct pcpu*), maxcpu);
      for (n = 0; n < maxcpu; n ++)
        cpu[n] = kvm_getpcpu(kd, n);

Данные о каждом процессоре FreeBSD бережно передает в «struct pcpu». Как раз из нее и будем получать информации о загруженности.

void* kvm_getpcpu(kvm_t *kd, int cpu);

Возвращает информацию по процессору (если он присутствует под номер «cpu»), или NULL — если информация недоступна (процессора нет).
В этой структуре нас интересуют «pc_cp_time» — статистика по процессору:

long pc_cp_time[CPUSTATES]; /* statclock ticks */

Обращаем внимание, как заполнен этот массив:

#define CP_USER    0
#define CP_NICE    1
#define CP_SYS     2
#define CP_INTR    3
#define CP_IDLE    4
#define CPUSTATES  5

То-есть, «cpu[n][CP_SYS]» — содержит количество тиков процессора, занятых обработкой системных вызовов, «cpu[n][CP_INTR]» — тики, потраченные на обработку прерываний. Тогда логично предположить — если через определенный промежуток времени повторно собрать информацию по процессору, и вычесть из нее первую — можно получить загруженность это процессора в тиках.

Записываем выше сказанное:

      // Запрашиваем данные по процессорам
      cpu1 = calloc(sizeof(struct pcpu*), maxcpu);
      for (n = 0; n < maxcpu; n ++)
        cpu1[n] = kvm_getpcpu(kd, n);
      // Ждем одну секунду
      sleep(1);
      // Второй замер
      cpu2 = calloc(sizeof(struct pcpu*), maxcpu);
      for (n = 0; n < maxcpu; n ++)
        cpu2[n] = kvm_getpcpu(kd, n);
      // Расчитываем загруженность
      for (n = 0; n < maxcpu; n ++)
        if (cpu2[n] && cpu1[n])
        {
          long long total = 0;
          for (i = 0; i < CPUSTATES; i ++)
          {
             cpu2[n]->pc_cp_time[i] -= cpu1[n]->pc_cp_time[i];
             total += cpu2[n]->pc_cp_time[i];
          }
          // Выводим информацию
          printf("CPU #%02d: user=%ld, sys=%ld, nice=%ld, intr=%ld, idle=%ld, total=%lld\n",
            cpu2[n]->pc_cpuid, cpu2[n]->pc_cp_time[CP_USER], cpu2[n]->pc_cp_time[CP_SYS],
            cpu2[n]->pc_cp_time[CP_NICE], cpu2[n]->pc_cp_time[CP_INTR],
            cpu2[n]->pc_cp_time[CP_IDLE], total);
        }

Осталось только перевести эти попугаи в понятные проценты. С этим просто — известно сколько было затрачено тиков всего, и сколько тиков конкретная нагрузка. Высчитываем долю вхождения каждой нагрузки в общую:

          // Информация в процентах
          printf("CPU #%02d: user=%0.2f%%, sys=%0.2f%%, nice=%0.2f%%, intr=%0.2f%%, idle=%0.2f%%\n",
            cpu2[n]->pc_cpuid, cpu2[n]->pc_cp_time[CP_USER]*100.0/total, cpu2[n]->pc_cp_time[CP_SYS]*100.0/total,
            cpu2[n]->pc_cp_time[CP_NICE]*100.0/total, cpu2[n]->pc_cp_time[CP_INTR]*100.0/total,
            cpu2[n]->pc_cp_time[CP_IDLE]*100.0/total);

Теперь собираем все вместе и смотрим что получилось. Листинг программы:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/pcpu.h>
#include <kvm.h>

int main()
{
  kvm_t *kd;
  // kvm descriptor
  kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm");
  if (kd)
  {
    int maxcpu;
    // Запрашиваем количество поддерживаемых системой процессоров
    maxcpu = kvm_getmaxcpu(kd);
    printf("maxcpu=%d\n", maxcpu);
    if (maxcpu > 0) {
      int i,n;
      struct pcpu **cpu1;
      struct pcpu **cpu2;
      // Запрашиваем данные по процессорам
      cpu1 = calloc(sizeof(struct pcpu*), maxcpu);
      for (n = 0; n < maxcpu; n ++)
        cpu1[n] = kvm_getpcpu(kd, n);
      // Ждем одну секунду
      sleep(1);
      // Второй замер
      cpu2 = calloc(sizeof(struct pcpu*), maxcpu);
      for (n = 0; n < maxcpu; n ++)
        cpu2[n] = kvm_getpcpu(kd, n);
      // Расчитываем загруженность
      for (n = 0; n < maxcpu; n ++)
        if (cpu2[n] && cpu1[n])
        {
          long long total = 0;
          for (i = 0; i < CPUSTATES; i ++)
          {
            cpu2[n]->pc_cp_time[i] -= cpu1[n]->pc_cp_time[i];
            total += cpu2[n]->pc_cp_time[i];
          }
          // Выводим информацию
          printf("CPU #%02d: user=%ld, sys=%ld, nice=%ld, intr=%ld, idle=%ld, total=%lld\n",
            cpu2[n]->pc_cpuid, cpu2[n]->pc_cp_time[CP_USER], cpu2[n]->pc_cp_time[CP_SYS],
            cpu2[n]->pc_cp_time[CP_NICE], cpu2[n]->pc_cp_time[CP_INTR],
            cpu2[n]->pc_cp_time[CP_IDLE], total);
          // Информация в процентах
          printf("CPU #%02d: user=%0.2f%%, sys=%0.2f%%, nice=%0.2f%%, intr=%0.2f%%, idle=%0.2f%%\n",
            cpu2[n]->pc_cpuid, cpu2[n]->pc_cp_time[CP_USER]*100.0/total, cpu2[n]->pc_cp_time[CP_SYS]*100.0/total,
            cpu2[n]->pc_cp_time[CP_NICE]*100.0/total, cpu2[n]->pc_cp_time[CP_INTR]*100.0/total,
            cpu2[n]->pc_cp_time[CP_IDLE]*100.0/total);
        }
      // Освобождаем ресурсы
      for (n = 0; n < maxcpu; n ++)
      {
        if (cpu1[n]) free(cpu1[n]);
        if (cpu2[n]) free(cpu2[n]);
      }
      free(cpu1);
      free(cpu2);
    }
    //
    kvm_close(kd);

    return 0;
  }
}

Или можно скачать

Компилируем и запускаем:

mira# clang cpu.c -o cpu.exe -lkvm
mira# ./cpu.exe
kvm: kvm_nlist: No such file or directory
kvm: kvm_nlist: No such file or directory
maxcpu=64
CPU #00: user=0, sys=0, nice=0, intr=3, idle=124, total=127
CPU #00: user=0.00%, sys=0.00%, nice=0.00%, intr=2.36%, idle=97.64%
CPU #01: user=1, sys=0, nice=0, intr=1, idle=125, total=127
CPU #01: user=0.79%, sys=0.00%, nice=0.00%, intr=0.79%, idle=98.43%
CPU #02: user=0, sys=1, nice=0, intr=1, idle=125, total=127
CPU #02: user=0.00%, sys=0.79%, nice=0.00%, intr=0.79%, idle=98.43%
CPU #03: user=2, sys=1, nice=0, intr=3, idle=121, total=127
CPU #03: user=1.57%, sys=0.79%, nice=0.00%, intr=2.36%, idle=95.28%
mira#

Наверно обратили внимание, что суммарно тиков на каждом процессоре 127? Дело в том, что ядро собирает статистику с определенной периодичностью. Если вкратце — то секунда делится на равное число тиков, и каждый тик помечается какой нагрузке он принадлежит. Посмотреть количество тиков в секунду можно в системной переменной «kern.clockrate», а именно «stathz»:

mira# sysctl kern.clockrate
kern.clockrate: { hz = 1000, tick = 1000, profhz = 8128, stathz = 127 }

На этом все :)

PS: Исходя из вышеописанного — имеет смысл в ядре устанавливать количество поддерживаемых процессоров равное количеству установленных. В моем случае — система собрана с поддержкой 64 процессов, хотя по факту используется только 4.

Пока комментариев нет.
Вы должны авторизоваться для отправки комментария.