понедельник, 12 октября 2009 г.

Анализ статистики CUPS

Все админы рано или поздно сталкиваются с необходимостью приобретения нового принтера. Сразу отмечу, что все что будет написано ниже, будет относиться исключительно к CUPS. Соответственно возникает вопрос, какой принтер брать, на какую нагрузку закладываться? Можно пойти несколькими путями:

  • Прикинуть как часто покупаются расходники и исходя из этого приблизительно оценить нагрузку
  • Переговорить с пользователями (самый бестолковый вариант)
  • Проанализировать лог-файл принт-сервера (тут возникает проблема, поскольку логи, как правило, хранятся весьма ограниченное время и полученные данные могут быть не отражающими реальное положение дел)
  • Просто купить тот, который понравился (чего уж греха таить, самый распространенный метод)
Мы пойдем другим путем!



Поскольку самым надежным методом определения текущей нагрузки на принтеры является анализ информации содержащейся в файле /var/log/cups/page_log, будем работать с ним. Но, как отмечалось ранее, тут возникает проблема получения информации о заданиях за достаточно длительный срок. Я решил этот вопрос путем складывания данных из page_log в БД MySQL, поскольку на машине где крутится CUPS он уже был.
Для начала определим структуру таблицы, в которую будем складывать записи из page_log. Для этого посмотрим на структуру лога:
HP_LaserJet_P2055dn user 9556 [12/May/2009:14:22:51 +0400] 1 2 - host
Здесь по порядку:

  • HP_LaserJet_P2055dn - имя принтера
  • user - имя пользователя
  • 9556 - номер задания
  • [12/May/2009:14:22:51 +0400] - дата и время печати
  • 1 - номер страницы в задании
  • 2 - количество копий страницы
  • host - хост, с которого производилась печать
Исходя из этого создаем базу данных cups_stat содержащую таблицу, например, tasks_log:
CREATE DATABSE  `cups_stat`;
USE `cups_stat`;

CREATE TABLE IF NOT EXISTS `tasks_log` (
  `id` bigint(10) NOT NULL auto_increment,
  `prn_name` varchar(50) NOT NULL,
  `user_name` varchar(50) NOT NULL,
  `task_id` int(10) NOT NULL,
  `p_date` datetime NOT NULL,
  `page_num` int(3) NOT NULL,
  `rep_num` int(3) NOT NULL,
  `host` varchar(50) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
Далее заводим пользователя в MySQL, даем ему права на работу с созданной БД.
Теперь самое интересное, нужно взять данные из лога и положить в БД. Изначально я реализовал это на shell с использованием AWK, но видимо я не очень хорошо знаю AWK, поскольку информация периодически задваивалась. В итоге был написан следующий скрипт на Perl:


#! /usr/bin/perl
use DBI;
use Time::Local;
#Хост MySQL
$dbhost='localhost';
#Пользователь MySQL
$dbuser='user';
#Пароль пользователя MySQL
$dbpass='password';
#IP Сервера CUPS
$CupsIP='192.168.0.10';
#Имя базы MySQL
$dbname='cups_stat';
#Полное имя page_log
$log='/var/log/cups/page_log';


%mnts = (
Jan => "01",
Feb => "02",
Mar => "03",
Apr => "04",
May => "05",
Jun => "06",
Jul => "07",
Aug => "08",
Sep => "09",
Oct => "10",
Nov => "11",
Dec => "12"
);
#Получаем дату и ID последнего задания в базе
#Подключение к DB
$dbh = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost",$dbuser, $dbpass) || die print "Can't connect";
$sth = $dbh->prepare(q{SELECT UNIX_TIMESTAMP(MAX(p_date)), MAX(task_id) FROM tasks_log});
$sth -> execute;
#print $sth."/n";
    $cols = $sth->{NUM_OF_FIELDS};   # кол-во строк в таблице
    $rows = $sth->rows;              # кол-во столбцов в таблице
    @FieldNames = @{ $sth->{NAME} }; # массив содержащий названия
                            # полей таблицы
    @row = $sth->fetchrow_array;
    $MaxDate=$row[0];
    $MaxTaskId=$row[1];
#    print $MaxTaskId."->".$MaxDate."\n";
#Открываем лог-файл cups
open (FH,$log)|| die print 'Не могу открыть файл! \n';
    while (< FH >)
{
@fields=split(' ');
# print $MaxTaskId." > ".$fields[2]."\n";
#Меняем localhost на IP серевера CUPS
if ($fields[8]=="localhost")
   {
   $fields[8]=$CupsIP;
   }
#Если ID задания из лог-файла больше либо равно ID последнего задания в базе 
#и его дата больше даты в базе пишем его в базу.
#Преобразуем формат даты в понятный для MySQL datetime
if ($fields[2]=>$MaxTaskId || ! $MaxTaskId)
{
   $year=substr($fields[3],8,4);
   $rmon=$mnts{substr($fields[3],4,3)};
   $mday=substr($fields[3],1,2);
   $sec=substr($fields[3],-2);
   $min=substr($fields[3],-5,2);
   $hours=substr($fields[3],-8,2);
   $udt=$year."-".$rmon."-".$mday." ".substr($fields[3],-8);
   $fields[3]=$udt;
   $TIME = timelocal($sec, $min, $hours, $mday, $rmon-1, $year);
if ($TIME>$MaxDate)
{
#Создаем запрос SQL и выполняем его
   $query="insert into tasks_log (prn_name, user_name, task_id, p_date, page_num, rep_num, host) values('".$fields[0]."','".$fields[1]."','".$fields[2]."','".$fields[3]."','".$fields[5]."','".$fields[6]."','".$fields[8]."')";
   $sth = $dbh->prepare(qq{$query});
   $sth -> execute;
}
}
}
# Закрываем лог-файл
close(FH);
# Отключаемся от базы
$sth -> finish;
$dbh -> disconnect;


exit;

Дальше прописываем выполнение скрипта в crontab через желаемый промежуток времени. У меня, например, каждые 30 минут.
Для удобства работы с MySQL можно поставить phpmyadmin, в нем же можно и анализировать данные. конструируя запросы. Я для этих целей написал WEB-интерфейс на PHP, получилось вот как то так:


Если кому нужно - пишите, могу выслать. Правда дописано не до конца, лень напала.
UPD: Про WEB-интерфейс читаем тут.
UPD2: Рабочий скрипт лежит тут

24 комментария:

  1. Добрый день. Очень интересная статистика вывода данных о печати, хотели бы сделать такую же у себя , если не трудно вы не могли бы поделиться скриптами для статистики.
    Если не трудно вышлите на почту sergegm@yandex.ru заранее благодарен.

    ОтветитьУдалить
  2. Все необходимые скрипты для сбора статистики есть в статье. Или интересует WEB-интерфейс?

    ОтветитьУдалить
  3. Да нас интересует именно WEB интерфейс.

    ОтветитьУдалить
  4. Хорошо, как вспомню что я там понаписал, сделаю инструкцию и выложу здесь. Сразу предупреждаю, что писалось все под себя и к тому же дописалось не все что хотелось, но статистику по пользователям и по принтерам посмотреть можно. :)

    ОтветитьУдалить
  5. Большое спасибо, жду.

    ОтветитьУдалить
  6. Gunther поставили Web интерфейс создали базу и столкнулись с такой проблемой при запуске скрипта выдает такую ошибку.
    17222 >
    Day '' out of range 1..31 at db line 70

    ОтветитьУдалить
  7. Какую версию CUPS используете? Выложите сюда пару строк из /var/log/cups/page_log. Должно быть что то типа HP_LaserJet_P2055dn user 231 [08/Apr/2010:09:44:50 +0400] 3 1 - 192.168.0.x

    ОтветитьУдалить
  8. Этот комментарий был удален автором.

    ОтветитьУдалить
  9. xerox4510l user 17086 [07/Apr/2010:11:07:43 +0400] 1 1 - 192.168.1.179
    xerox4510l Domen\s.serge 17087 [07/Apr/2010:11:07:57 +0400] 1 1 - 192.168.1.163
    получается вот так , часть пользователей домено часть не в домене Версия Cups 1.3.7

    ОтветитьУдалить
  10. Ищем в скрипте строку
    open (FH,$log)|| die print 'Не могу открыть файл! \n';
    while ()
    и правим while () на while (< FH>)
    < FH> должно быть без пробелов!!!
    При публикации поста парсер принимает хэндлер файла за HTML тэг. Сорри, не уследил. :)

    ОтветитьУдалить
  11. Да теперь все без проблем спасибо, можно еще пару вопросов , а можно ли с другой машины с Cups системой сделать отправку логов в базу MySQL на сервере , и второй вопрос если я в crontab -e задам параметры 30 * * * * /var/www/cups/script.pl и на скрипт даю права 750 то не надо будет каждый день давать на фаил /var/log/cups/page_log доступ?

    ОтветитьУдалить
  12. 1. Можно, только будет глючить, поскольку на разных серверах могут быть задания с одинаковым временем и номером, а в БД нет идентификатора сервера CUPS. Если добавить в таблицу tasks_log поле идентификатор сервера и подправить скрипт, чтобы он это поле учитывал, то без проблем. Разрешаете MySQL соединения с удаленных клиентов и вперед. Если принтеры на серверах не имеют одинаковых имен, то WEB-интерфейс врать не станет, если есть, то его тоже нужно править. :)
    2. У меня права на page_log стоят 644 по умолчанию, так что их и править то незачем, если есть сомнения, добавьте пользователя, от имени которого запускается скрипт в группу lp. gpasswd -a user lp.

    ОтветитьУдалить
  13. Спасибо, будем пробывать.

    ОтветитьУдалить
  14. Нашлась ошибка в скрипте единственное я не знаю где, но при повторном выполнение скрипта данные в базу дублируются и получается копия статистики если было напечатано 300 станиц становится 600 скорей всего ошибка #Если ID задания из лог-файла больше либо равно ID последнего задания в базе
    #и его дата больше даты в базе пишем его в базу. но в Perl я не силен)

    ОтветитьУдалить
  15. Странно, может быть еще чего при публикации отъелось. :)
    Выложил свой скрипт, который у меня на сервере крутится, см. UPD2 внизу поста.

    ОтветитьУдалить
  16. Такого файла не существует или он был удален из-за нарушения авторских прав.

    ОтветитьУдалить
  17. Может кто сталкивался с такой проблемой. Есть принтер XeroxPhaser 4510. При печати в логах CUPS отображается количество страниц и копий всегда 1. Есть решение данной проблемы?

    ОтветитьУдалить
  18. Не сталкивался, попробуй переустановить принтер, если не поможет - обновить CUPS/драйвер принтера.

    ОтветитьУдалить
  19. > $fields[8]=="localhost"

    Это бессмысленное сравнение. Правильно: $fields[8] eq "localhost"

    ОтветитьУдалить
    Ответы
    1. Но, удивительным образом, отрабатывает корректно. Однако, спорить не буду - с perl'ом я на Вы....

      Удалить
  20. А поделитесь пожалуйста веб-интерфейсом, а то ссылочки уже не актуальны.

    ОтветитьУдалить
  21. Еще пришла в голову такая идея, crontab конечно не плох, но чтобы выводилась актуальная статистика в реальном времени сделать скрипт, который будет отслеживать изменения в размере файла и в случае, если таковые будут запускать perl-скрипт.
    Что-то вроде:
    #!/bin/bash

    #$SCRIPT_PATH="./Cups_stat.pl"

    i=1
    modyfy=0
    while true
    do

    pred=$(stat -c %s "/var/log/cups/page_log-20180318");


    echo $pred
    echo $modyfy
    echo $i
    if [ "$pred" -eq "$modyfy" ]
    then
    modyfy=$pred
    echo "Нет изменений"
    fi

    if [ "$pred" -ne "$modyfy" ]
    then
    echo "Что-то изменилось"

    #source $SCRIPT_PATH "./Cups_stat.pl"

    #!/usr/bin/env perl
    ./Cups_stat.pl

    modyfy=$pred
    fi

    sleep 60
    $i = '2';

    done


    Его конечно еще можно демонизировать, а также всячески улучшить и доработать.
    Конечно и очистка самого лога не помешала бы.
    Но даже в таком виде на текущий момент все работает довольно не плохо)

    ОтветитьУдалить