Программирование для Linux. Профессиональный подход.

11.3.4. Статистика выполняющихся процессов.

Модуль processes.so (исходный текст приведен в листинге 11.9) сложнее остальных модулей. Он генерирует страницу, в которой содержится таблица процессов, выполняющихся в данный момент на сервере. Каждому процессу отводится в таблице одна строка. В этой строке указан идентификатор процесса, имя исполняемого файла, имена владельца и группы, которым принадлежит процесс, а также размер резидентной части процесса.

Листинг 11.9. (processes.c) Серверный модуль, отображающий таблицу процессов.

#include <assert.h>

#include <dirent.h>

#include <fcntl.h>

#include <grp.h>

#include <pwd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <sys/uio.h>

#include <unistd.h>

#include "server.h".

/* Эта функция записывает в аргументы UID и GID.

   идентификаторы пользователя и группы, которым.

   принадлежит процесс с указанным идентификатором,

   в случае успешного завершения возвращается нуль,

   иначе -- ненулевое значение. */

Static int get_uid_gid(pid_t pid, uid_t* uid, gid_t* gid) {

 char dir_name[64];

 struct stat dir_info;

 int rval;

 /* Формирование имени каталога процесса.

    в файловой системе /proc. */

 snprintf(dir_name, sizeof(dir_name), "/proc/%d", (int)pid);

 /* Получение информации о каталоге. */

 rval = stat(dir_name, &dir_info);

 if (rval != 0).

  /* Каталог не найден. Возможно, процесс больше.

     не существует. */

  return 1;

 /* Убеждаемся в том, что это действительно каталог. */

 assert(S_ISDIR(dir_info.st_mode));

 /* Определяем интересующие нас идентификаторы. */

 *uid = dir_info.st_uid;

 *gid = dir_info.st_gid;

 return 0;

}

/* Эта функция находит имя пользователя,

   соответствующее заданному идентификатору.

   Возвращаемый буфер должен быть удален.

   в вызывающей функции. */

Static char* get_user_name(uid_t uid) {

 struct passwd* entry;

 entry = getpwuid(uid);

 if (entry == NULL).

  system_error("getpwuid");

 return xstrdup(entry->pw_name);

}

/* Эта функция находит имя группы, соответствующее.

   заданному идентификатору, возвращаемый буфер.

   должен быть удален в вызывающей функции. */

Static char* get_group_name(gid_t gid) {

 struct group* entry;

 entry = getgrgid(gid);

 if (entry == NULL).

  system_error("getgrgid");

 return xstrdup(entry->gr_name);

}

/* Эта функция находит имя программы, которую выполняет.

   процесс с заданным идентификатором. Возвращаемый буфер.

   должен быть удален в вызывающей функции. */

Static char* get_program_name(pid_t pid) {

 char file_name[64];

 char status_info[256];

 int fd;

 int rval;

 char* open_paren;

 char* close_paren;

 char* result;

 /* Генерируем имя файла "stat", находящегося в каталоге.

    данного процесса в файловой системе /proc,

    и открываем этот файл. */

 snprintf(file_name, sizeof(file_name), "/proc/%d/stat",

  (int)pid);

 fd = open(file_name, O_RDONLY);

 if (fd == 1).

  /* Файл не удалось открыть. Возможно, процесс.

     больше не существует. */

  return NULL;

 /* Чтение содержимого файла.

 rval = read(fd, status_info, sizeof(status_info) — 1);

 close(fd);

 if (rval <= 0).

  /* По какой-то причине файл не удалось прочитать, завершаем.

     работу. */

  return NULL;

 /* Завершаем прочитанный текст нулевым символом. */

 status_info[rval] = '\0';

 /* Имя программы -- это второй элемент файла, заключенный в.

    круглые скобки. Находим местоположение скобок. */

 open_paren = strchr(status_info, '(');

 close_paren = strchr(status_info, ')');

 if (open_paren == NULL ||

  close_paren == NULL || close_paren < open_paren).

  /* He удалось найти скобки, завершаем работу. */

  return NULL;

 /* Выделение памяти для результирующей строки */

 result = (char*)xmalloc(close_paren — open_paren);

 /* Копирование имени программы в буфер. */

 strncpy(result, open_paren + 1, close_paren - open_paren — 1);

 /* Функция strncpy() не завершает строку нулевым символом,

    приходится это делать самостоятельно. */

 result[close_paren - open_paren - 1] = '\0';

 /* Конец работы. */

 return result;

}

/* Эта функция определяет размер (в килобайтах) резидентной.

   части процесса с заданным идентификатором.

   В случае ошибки возвращается -1. */

Static int get_rss(pid_t pid) {

 char file_name[64];

 int fd;

 char mem_info[128];

 int rval;

 int rss;

 /* Генерируем имя файла "statm", находящегося в каталоге.

    данного процесса в файловой системе proc. */

 snprintf(file_name, sizeof(file_name), "/proc/%d/statm",

  (int)pid);

 /* Открытие файла. */

 fd = open(file_name, O_RDONLY);

 if (fd == -1).

  /* Файл не удалось открыть. Возможно, процесс больше не.

     существует. */

  return -1;

 /* Чтение содержимого файла. */

 rval = read(fd, mem_info, sizeof(mem_info) — 1);

 close(fd);

 if (rval <= 0).

  /* Файл не удалось прочитать, завершаем работу. */

  return -1;

 /* Завершаем прочитанный текст нулевым символом. */

 mem_infо[rval] = '\0';

 /* Определяем размер резидентной части процесса. Это второй.

    элемент файла. */

 rval = sscanf(mem_info, "%*d %d", &rss);

 if (rval != 1).

  /* Содержимое файла statm отформатировано непонятным.

     образом. */

  return -1;

 /* Значения в файле statm приведены в единицах, кратных размеру.

    системной страницы. Преобразуем в килобайты. */

 return rss * getpagesize() / 1024;

}

/* Эта функция генерирует строку таблицы для процесса.

   с заданным идентификатором. Возвращаемый буфер должен.

   удаляться в вызывающей функции, в случае ошибки.

   возвращается NULL. */

Static char* format_process_info(pid_t pid) {

 int rval;

 uid_t uid;

 gid_t gid;

 char* user_name;

 char* group_name;

 int rss;

 char* program_name;

 size_t result_length;

 char* result;

 /* Определяем идентификаторы пользователя и группы, которым.

    принадлежит процесс. */

 rval = get_uid_gid(pid, &uid, &gid);

 if (rval != 0).

  return NULL;

 /* Определяем размер резидентной части процесса. */

 rss = get_rss(pid);

 if (rss == -1).

  return NULL;

 /* Определяем имя исполняемого файла процесса. */

 program_name = get_program_name(pid);

 if (program_name == NULL).

  return NULL;

 /* Преобразуем идентификаторы пользователя и группы в имена. */

 user_name = get_user_name(uid);

 group_name = get_group_name(gid);

 /* Вычисляем длину строки, в которую будет помещен результат,

    и выделяем для нее буфер. */

 result_length =

  strlen(program_name) + strlen(user_name) +

  strlen(group_name) + 128;

 result = (char*)xmalloc(result_length);

 /* Форматирование результата. */

 snprintf(result, result_length,

  "<tr><td align=\" right\">%d</td><td><tt>%s</tt></td><td>%s</td>"

  "<td>%s</td><td align= \"right\">%d</td></tr>\n",

  (int)pid, program_name, user_name, group_name, rss);

 /* Очистка памяти. */

 free(program_name);

 free(user_name);

 free(group_name);

 /* Конец работы. */

 return result;

}

/* HTML-код начала страницы, содержащей таблицу процессов. */

Static char* page_start =

 "<html>\n".

 " <body>\n".

 "  <table cellpadding=\"4\" cellspacing=\"0\" border=\"1\">\n".

 "   <thead>\n".

 "    <tr>\n".

 "     <th>PID</th>\n".

 "     <th>Program</th>\n".

 "     <th>User</th>\n".

 "     <th>Group</th>\n".

 "     <th>RSS&nbsp;(KB)</th>\n".

 "    </tr>\n".

 "   </thead>\n".

 "   <tbody>\n";

/* HTML-код конца страницы, содержащей таблицу процессов. */

Static char* page_end =

 "   </tbody>\n".

 "  </table>\n".

 " </body>\n".

 "</html>\n";

Void module_generate(int fd) {

 size_t i;

 DIR* proc_listing;

 /* Создание массива iovec. В этот массив помещается выходная.

    информации, причем массив может увеличиваться динамически. */

 /* Число используемых элементов массива */

 size_t vec_length = 0;

 /* выделенный размер массива */

 size_t vec_size = 16;

 /* Массив элементов iovec. */

 struct iovec* vec =

  (struct iovec*)xmalloc(vec_size *

  sizeof(struct iovec));

 /* Сначала в массив записывается HTML-код начала страницы. */

 vec[vec_length].iov_base = page_start;

 vec[vec_length].iov_len = strlen(page_start);

 ++vec_length;

 /* Получаем список каталогов в файловой системе /proc. */

 proc_listing = opendir("/proc");

 if (proc_listing == NULL).

  system_error("opendir");

 /* Просматриваем список каталогов. */

 while (1) {

  struct dirent* proc_entry;

  const char* name;

  pid_t pid;

  char* process_info;

  /* Переходим к очередному элементу списка. */

  proc_entry = readdir(proc_listing);

  if (proc_entry == NULL).

   /* Достигнут конец списка. */

   break;

  /* Если имя каталога не состоит из одних цифр, то это не.

     каталог процесса; пропускаем его. */

  name = proc_entry->d_name;

  if (strspn(name, "0123456789") != strlen(name)).

   continue;

  /* Именем каталога является идентификатор процесса. */

  pid = (pid_t)atoi(name);

  /* генерируем HTML-код для строки таблицы, содержащей.

     описание данного процесса. */

  process_info = format_process_info(pid);

  if (process_info == NULL).

   /* Произошла какая-то ошибка. Возможно, процесс уже.

      завершился. Создаем строку-заглушку. */

   process_info =

    "<tr><td colspan=\"5\">ERROR</td></tr>";

  /* Убеждаемся в том, что в массиве iovec достаточно места.

     для записи буфера (один элемент будет добавлен в массив.

     по окончании обработки списка процессов). Если места.

     не хватает, удваиваем размер массива. */

  if (vec_length == vec_size - 1) {

   vec_size *= 2;

   vec = xrealloc(vec, vec_size - sizeof(struct iovec));

  }

  /* Сохраняем в массиве информацию о процессе. */

  vec[vec_length].iov_base = process_info;

  vec[vec_length].iov_len = strlen(process_info);

  ++vec_length;

 }

 /* Конец обработки списка каталогов */

 closedir(proc_listing);

 /* Добавляем HTML-код конца страницы. */

 vec[vec_length].iov_base = page_end;

 vec[vec_length].iov_len = strlen(page_end);

 ++vec_length;

 /* Передаем всю страницу клиенту. */

 writev(fd, vec, vec_length);

 /* Удаляем выделенные буферы. Первый и последний буферы.

    являются статическими, поэтому не должны удаляться. */

 for (i = 1; i < vec_length - 1; ++i).

  free(vec[i].iov_base);

 /* Удаляем массив iovec. */

 free(vec);

}

Задача сбора информации о процессах и представления ее в виде HTML-таблицы разбивается на ряд более простых операций.

■ Функция get_uid_gid() возвращает идентификатор пользователя и группы, которым принадлежит процесс. Для этого вызывается функция stat() (описана в приложении Б, "Низкоуровневый ввод-вывод"), берущая информацию из каталога процесса в файловой системе /proc.

■ Функция get_user_name() возвращает имя пользователя, соответствующее заданному идентификатору. Она просто вызывает библиотечную функцию getpwuid(), которая обращается к файлу /etc/passwd и возвращает копию строки из него. Функция get_group_name() находит имя группы по заданному идентификатору. Она вызывает функцию getgrgid().

■ Функция gеt_program_name() возвращает имя программы, соответствующей заданному процессу. Эта информация извлекается из файла stat, находящегося в каталоге процесса в файловой системе /proc (см. раздел 7.2, "Каталоги процессов"). Мы поступаем так, а не проверяем символические ссылки exe или cmdline, поскольку последние недоступны, если серверный процесс не принадлежит тому же пользователю, что и проверяемый процесс.

■ Функция get_rss() определяет объем резидентной части процесса. Эта информация содержится во втором элементе файла statm (см. раздел 7.2.6, "Статистика использования процессом памяти"), находящегося в каталоге процесса в файловой системе /proc.

■ Функция format_process_info() генерирует набор HTML-тэгов для строки таблицы, представляющей заданный процесс. Здесь вызываются все вышеперечисленные функции.

■ Функция module_generate() генерирует HTML-страницу с таблицей процессов. Выводная информация включает начальный HTML-блок (переменная page_start), строки с информацией о процессах (создаются функцией format_process_info()) и конечный HTML-блок (переменная page_end).

Функция module_generate() определяет идентификаторы процессов, проверяя содержимое файловой системы /proc. Для получения и анализа списка каталогов вызываются функции opendir() и readdir() (описаны в приложении Б, "Низкоуровневый ввод-вывод''). Из данного списка отбираются элементы, имена которых состоят из одних цифр: это каталоги процессов.

Поскольку в таблице может содержаться достаточно большое число строк, последовательная запись их в сокет с помощью функции write() приведет к ненужному повышению трафика. Для оптимизации числа передаваемых пакетов используется функция writev() (описана в приложении Б, "Низкоуровневый ввод-вывод"). Для нее создается массив vec, состоящий из элементов типа iovec. Так как число процессов не известно заранее приходится начинать с маленького массива и увеличивать его по мере необходимости. В переменной vec_length содержится число используемых элементов массива vec, а в переменной vec_size — число выделенных элементов. Когда эти переменные становятся почти равными друг другу, размер массива удваивается с помощью функции xrealloc(). По окончании работы с массивом удаляются все адресуемые в нем строки, а также сам массив.