Мониторим всё: расширение агентов Windows и Linux при помощи скриптов

  • Tutorial
Если нам нужно мониторить состояние серверов и прочих компьютеризированных рабочих мест при помощи Zabbix, то это можно сделать двумя способами.

Первый способ — это при помощи SNMP-запросов, с отправкой которых Zabbix замечательно справляется. Так можно вытащить и загрузку сетевых интерфейсов, и загрузку процессора, памяти. Поверх этого, производители сервера могут выдать нам по SNMP еще много информации о состоянии железа.

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

image

Что делать, если того, что мы хотим контролировать через Zabbix нет в списке возможностей Zabbix агента? Ждать пока это имплементируют разработчики в следующем релизе? Не обязательно.

Нам оставили несколько стандартных интерфейсов для того, чтобы расширить возможности Заббикса по мониторингу серверов настолько, насколько позволит нам наша фантазия и наличие свободного времени на написание скриптов. Интерфейсы эти UserParameter и zabbix_sender. О первом и пойдет речь, а в качестве примеров будет показано как можно собирать состояние S.M.A.R.T жестких дисков и контролировать, когда кто-то удаляет или устанавливает новые программы на своей Windows-машине.

Немного матчасти


Если вы уже хоть раз настраивали Zabbix агент на сервере, то начать использовать UserParameter не составит труда. Чтобы добавить новый параметр нужно сделать несколько вещей:

  • Добавить в конце конфигурационного файла zabbix_agentd.conf строчку вида

UserParameter=<ключ>,<команда>
где:

<ключ> — уникальное имя, которое мы придумываем сами. Будем его использовать при настройке элемента данных в Zabbix.
<команда> — команда, которую нужно выполнить на наблюдаемом узле сети.

А вот сразу очень простой пример, который лежит в каждом стандартном конфиге для Linux:

UserParameter=system.test,who|wc -l

Итак, ключ здесь system.test, а выполняем команду who | wc -l, которая возвращает нам количество открытых сессий в системе. Добавляем (или раскомментируем данную строчку если уже есть), идем дальше.

  • В Веб-консоли Zabbix создать новый элемент данных с ключом, который мы использовали, если брать пример выше, то это system.test.

Для этого нажимаем «Создать элемент данных»
image

и затем выставляем ключ такой же, как указали в конфиг-файле, а тип Zabbix агент:

image
  • Перезагрузить Zabbix агента, чтобы изменения в конфиг-файле вступили в силу

Наблюдаем результат в последних данных:

image

Мониторинг SMART через UserParameter


Пример выше имеет мало практического применения, учитывая, что уже итак существует стандартный ключ system.users.num, который делает ровно тоже самое.

Так что теперь рассмотрим пример, который уже больше будет походить на реалистичный.

Если нам интересно мониторить момент, когда пора планово менять жесткие диски, то есть два варианта:

  1. Если диски за аппаратным RAID-контроллером, то, как правило, сами диски операционная система «не видит». Поэтому ищем способы как вытащить информацию о состоянии жестких дисков через утилиты или SNMP-сабагента, которые нам любезно предоставил(или не предоставил) производитель RAID-контроллера. Для каждой отдельной серии контроллеров свой путь до этой информации.
  2. Если речь идет о просто рабочих станциях, серверах с софтовом RAID и т.д., то тогда к дискам есть доступ из операционной системы, и мы вольны использовать различные утилиты для чтения их статуса. В случае Zabbix нам подходит утилита smartctl, из пакета SMARTMONTOOLS.

В Debian установка SMARTMONTOOLS сводится к:

sudo apt-get install smartmontools

и утилита готова к использованию.

Для каждого диска, который есть в системе сначала проверим, что SMART включен:

sudo smartctl -i /dev/sda | grep SMART
SMART support is: Available - device has SMART capability.
SMART support is: Enabled

если вдруг SMART поддерживается диском, но выключен, то активируем его:

sudo smartctl -s on -S on -o on /dev/sda
smartctl version 5.37 [i686-pc-linux-gnu] Copyright (C) 2002-6 Bruce Allen
Home page is http://smartmontools.sourceforge.net/

=== START OF ENABLE/DISABLE COMMANDS SECTION ===
SMART Enabled.
SMART Attribute Autosave Enabled.
SMART Automatic Offline Testing Enabled every four hours.

Теперь мы можем проверять статус SMART командой:

sudo smartctl -H /dev/sda |grep "test"| cut -f2 -d: |tr -d " "

Именно эту команду мы и запишем в наш zabbix_agentd.conf:

UserParameter=uHDD.health,sudo smartctl -H /dev/sda |grep "test"| cut -f2 -d: |tr -d " "

где uHDD.health — ключ.

Мониторинг SMART через Flexible UserParameter


Тут возникает резонный вопрос, как быть если дисков два. Легче всего решить эту проблему поможет способность UserParameter передавать параметры агенту, про которую мы еще не упоминали. Но делается все очень просто, сразу пример:

UserParameter=uHDD.health.[*],sudo smartctl -H /dev/$1 |grep "test"| cut -f2 -d: |tr -d " "

В веб-интерфейсе Zabbix в ключе мы будем подставлять параметры в квадратные скобки вместо *. Например, для одного элемента данных мы напишем sda, а для другого sdb. В команде этот параметр найдет отражение там, где стоит переменная $1.

image

Создадим для второго диска элемент данных:

image

И через некоторое время сможем наблюдать результат в последних данных:

image

Мониторинг SMART через Flexible UserParameter c Low-level Discovery


Все получилось. Но тут возникает резонный вопрос, как быть если дисков не два, а двадцать два. И тут нам пригодится замечательная возможность низкоуровнего обнаружения (LLD), про которую мы уже говорили.

Низкоуровневое обнаружение позволяет системе мониторинга обнаруживать какое количество однотипных элементов присутствует на узле сети и динамически по шаблону создавать необходимые элементы данных, триггеры и графики для этих элементов. «Из коробки» системе доступна возможность находить файловые системы, сетевые интерфейсы и SNMP OID'ы. Однако, и здесь разработчики оставили возможность дополнить стандартные возможности, нужно просто передать в систему информацию о том, какие элементы обнаружены в формате JSON. Этим и воспользуемся.

Создадим маленький скрипт на perl, smartctl-disks-discovery.pl. Он будет находить все диски в системе и выводить эту информацию в JSON, передавая также информацию, включен ли у диска SMART или нет, а также попытается сам включить SMART, если он выключен:

#!/usr/bin/perl

#must be run as root

$first = 1;

print "{\n";
print "\t\"data\":[\n\n";

for (`ls -l /dev/disk/by-id/ | cut -d"/" -f3 | sort -n | uniq -w 3`) {
    #DISK LOOP
    $smart_avail=0;
    $smart_enabled=0;
    $smart_enable_tried=0;

    #next when total 0 at output
    if ($_ eq "total 0\n") {
        next;
    }

    print "\t,\n" if not $first;
    $first = 0;

    $disk =$_;
    chomp($disk);

    #SMART STATUS LOOP
    foreach(`smartctl -i /dev/$disk | grep SMART`) {
        $line=$_;

        # if SMART available -> continue
        if ($line = /Available/){
            $smart_avail=1;
            next;
        }

        #if SMART is disabled then try to enable it (also offline tests etc)
        if ($line = /Disabled/ & $smart_enable_tried == 0){

            foreach(`smartctl -i /dev/$disk -s on -o on -S on | grep SMART`) {

                if (/SMART Enabled/){
                    $smart_enabled=1;
                    next;
                }
            }
            $smart_enable_tried=1;
        }

        if ($line = /Enabled/){
            $smart_enabled=1;
        }
    }

    print "\t{\n";
    print "\t\t\"{#DISKNAME}\":\"$disk\",\n";
    print "\t\t\"{#SMART_ENABLED}\":\"$smart_enabled\"\n";
    print "\t}\n";

}

print "\n\t]\n";
print "}\n";


При запуске скрипт выдает:
$ /usr/local/bin/smartctl-disks-discovery.pl


{
"data":[
    {
        "{#DISKNAME}":"md0",
        "{#SMART_ENABLED}":"0"
    },
    {
        "{#DISKNAME}":"md1",
        "{#SMART_ENABLED}":"0"
    },
    {
        "{#DISKNAME}":"sda",
        "{#SMART_ENABLED}":"1"
    },
    {
        "{#DISKNAME}":"sdb",
        "{#SMART_ENABLED}":"1"
    }]
}

Теперь, для того чтобы скрипт автоматически запускался Zabbix'ом, просто добавим еще один UserParameter в zabbix_agentd.conf:

UserParameter=uHDD.discovery,sudo /usr/local/bin/smartctl-disks-discovery.pl

Покончив с настройкой конфига, переходим в веб-интерфейс, где создаем новое правило обнаружения для smartctl:

image

Обратите внимание на ключ и на фильтр, ({#SMART_ENABLED}=1) благодаря последнему будут добавляться только те обнаруженные диски, которые поддерживают SMART. Теперь мы можем переписать два наших элемента данных для дисков sda и sdb в один прототип элементов данных, просто заменив имя диска на макрос {#DISKNAME}:

image

Последнее, перед тем, как Zabbix сможет запускать команды, которые мы прописали в zabbix_agentd.conf из-под root и мониторить SMART, нужно добавить разрешения для его пользователя запускать эту команду без ввода пароля, для этого добавим в /etc/sudoers строчку:

zabbix ALL= (ALL) NOPASSWD: /usr/sbin/smartctl,/usr/local/bin/smartctl-disks-discovery.pl

Готовый шаблон для мониторинга SMART с остальными элементами данных, триггерами прикладываю, так же как и настроенный под него конфиг.

Контроль за установкой новых программ на Windows


Zabbix агент, установленный на Windows, точно также может быть расширен через UserParameter, только команды будут уже другие. Хотя, например, smartctl — кроссплатформенная утилита, и точно также можно ее использовать для контроля за жесткими дисками в Windows.

Кратко рассмотрим еще другой пример. Задача получать уведомление каждый раз, когда пользователь самостоятельно удаляет или устанавливает программы.
Для этого будем использовать наш vbs-скрипт:

uDiffPrograms.vbs
'KNOWN ISSUE: If Application name conatins '-' symbol then e-mail alert containing software list will be sent all on one line instead of each packet on a single line
variable=InstalledApplications(".")
'WScript.Echo strConvert(variable,"Windows-1251","cp866")
Const ForReading = 1
zabbix_dir="C:\zabbix\"
Set objFSO = CreateObject("Scripting.FileSystemObject")
'Create old file if does not exist
If objFSO.FileExists(zabbix_dir&"uDiffPrograms_old.txt")=0 Then
Set objFile4 = objFSO.CreateTextFile(zabbix_dir&"uDiffPrograms_old.txt")
objFile4.WriteLine variable
objFile4.Close
Call ConvertCharsetFile("0x0")
WScript.Quit
End if
'Create 'new' file
Set objFile3 = objFSO.CreateTextFile(zabbix_dir&"uDiffPrograms_new.txt")
objFile3.WriteLine variable
objFile3.Close
'Compare old and new files
Set objArgs = Wscript.Arguments
Set objFile5= objFSO.GetFile(zabbix_dir&"uDiffPrograms_new.txt")
Set objFile6 = objFSO.GetFile(zabbix_dir&"uDiffPrograms_old.txt")

If objFile5.Size <> objFile6.Size Then
' Wscript.Echo "The file is different."
Else
'Wscript.Echo "They are the same."

objFSO.DeleteFile zabbix_dir&"uDiffPrograms_new.txt"
Call ConvertCharsetFile("0x0")
WScript.Quit

End If
'Search for removed applications
Set objFile2 = objFSO.OpenTextFile(zabbix_dir&"uDiffPrograms_old.txt", ForReading)

Do Until objFile2.AtEndOfStream
strAddress2 = objFile2.ReadLine
If InStr(variable, strAddress2&vbCrLf) = 0 Then
strNotCurrent2 = strNotCurrent2 & strAddress2 & vbCrLf
End If
Loop
objFile2.Close

'Search for installed applications
Set objFile1 = objFSO.OpenTextFile(zabbix_dir&"uDiffPrograms_old.txt", ForReading)

oldvar = objFile1.ReadAll

objFile1.Close
objFSO.DeleteFile zabbix_dir&"uDiffPrograms_old.txt"
Set objFile2 = objFSO.OpenTextFile(zabbix_dir&"uDiffPrograms_new.txt", ForReading)

Do Until objFile2.AtEndOfStream
strAddress = objFile2.ReadLine
If InStr(oldvar, strAddress&vbCrLf) = 0 Then
strNotCurrent = strNotCurrent & strAddress & vbCrLf
End If
Loop
objFile2.Close
'Rename C:\zabbix\uDiffPrograms_new.txt to C:\zabbix\uDiffPrograms_old.txt
objFSO.MoveFile zabbix_dir&"uDiffPrograms_new.txt" , zabbix_dir&"uDiffPrograms_old.txt"
'Output
if strNotCurrent <> "" and strNotCurrent2 <> "" then
Call ConvertCharsetFile("Новые программы были установлены:" & vbCrLf & strNotCurrent & vbCrLf & "Следующие программы были удалены:" & vbCrLf & strNotCurrent2)
Wscript.Quit
End if
if strNotCurrent <> "" then
Call ConvertCharsetFile("Новые программы были установлены:" & vbCrLf & strNotCurrent)
End if
if strNotCurrent2 <> "" then
Call ConvertCharsetFile("Следующие программы были удалены:" & vbCrLf & strNotCurrent2)
End If

Function InstalledApplications(node)
'''with Versions
Const HKLM = &H80000002 'HKEY_LOCAL_MACHINE
Set oRegistry = GetObject("winmgmts://" _
& node & "/root/default:StdRegProv")
sBaseKey = _
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"
iRC = oRegistry.EnumKey(HKLM, sBaseKey, arSubKeys)

For Each sKey In arSubKeys

iRC = oRegistry.GetStringValue( _
HKLM, sBaseKey & sKey, "DisplayName", sValue)

If iRC <> 0 Then
oRegistry.GetStringValue _
HKLM, sBaseKey & sKey, "QuietDisplayName", sValue
End If

If sValue <> "" and instr(sValue, "KB")=0 Then
'instr(sValue, "KB")=0 - to exlude KB-indexed Microsoft Patches
If instr(InstalledApplications, sValue&vbCrLf)=0 then
'and instr(InstalledApplications, sValue&vbCrLf)=0 - to exlude possible dublicates
InstalledApplications = _
InstalledApplications & sValue & vbCrLf
End If
End If
Next

End Function

Function ConvertCharsetFile(input)
Const adTypeBinary = 1
Const adTypeText = 2
Const bOverwrite = True
Const bAsASCII = False

'Write to temp file
Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FileExists( zabbix_dir&"uDiffPrograms_temp.txt" ) Then objFSO.DeleteFile zabbix_dir&"uDiffPrograms_temp.txt"
Set objFile3 = objFSO.CreateTextFile(zabbix_dir&"uDiffPrograms_temp.txt")
objFile3.WriteLine input
objFile3.Close

Dim oFS : Set oFS = CreateObject( "Scripting.FileSystemObject" )

Dim sFFSpec : sFFSpec = oFS.GetAbsolutePathName( zabbix_dir&"uDiffPrograms_temp.txt" )

Dim oFrom : Set oFrom = CreateObject( "ADODB.Stream" )
Dim sFrom : sFrom = "windows-1251"
Dim oTo : Set oTo = CreateObject( "ADODB.Stream" )
Dim sTo : sTo = "utf-8"

oFrom.Type = adTypeText
oFrom.Charset = sFrom
oFrom.Open
oFrom.LoadFromFile sFFSpec

oTo.Type = adTypeText
oTo.Charset = sTo
oTo.Open
oTo.WriteText oFrom.ReadText
oFrom.Close
If oFS.FileExists( sFFSpec ) Then oFS.DeleteFile sFFSpec
oTo.SaveToFile sFFSpec
oTo.Close
End Function
'=============================================================================


Для его интеграции с Zabbix добавим UserParameter в конфиг-файл:

UserParameter=uDiffPrograms, cscript.exe /nologo "C:\zabbix\uDiffPrograms.vbs" & type C:\zabbix\uDiffPrograms_temp.txt

Добавим элемент данных в шаблон для Windows:

image

Добавим триггер:

image

и действие, которое будет отправлять e-mail уведомление:

image

Весь процесс мониторинга выглядит так: каждый час запускается скрипт Zabbix агентом, который сравнивает два списка программ: текущий и предыдущий. Затем скрипт выписывает все изменения в отдельный файл. Если же изменений нет, то в файл пишется 0x0

Содержимое файла уходит на Zabbix сервер, где поднимается триггер в случае, если значение элемента данных uDiffProgramms отлично от 0x0. Затем отдельное действие отправляет по почте уведомление со списком того, что было установлено или удалено на данном компьютере:



В итоге


UserParameter — отличная и простая возможность расширить функционал системы самостоятельно. Стоит упомянуть и альтернативы: zabbix_sender, который, например, подойдет для тех случаев, когда нужно отправлять данные в Zabbix не по расписанию, (как это делает UserParameter), а по какому-то событию; и system.run[], который похож на UserParameter, но удобнее тем, что не нужно вносить изменения во все конфиги агентов, достаточно просто добавить этот элемент данных в шаблон. Более того, в следующем крупном релизе Zabbix 2.2 нас ожидает еще один новый способ расширить возможности агента- это подключаемые модули. Ждем с нетерпением!

Вот так, считайте, что если вы можете узнать что-то о системе скриптом или командой, значит, вы всегда можете передать это в Zabbix.
Zabbix
119,00
Компания
Поделиться публикацией

Похожие публикации

Комментарии 24

    –1
    Очень мощная штуковина, видимо для грандиозных проектов. В данный момент выбираю софт для мониторинга одного (может парочки) вебсерверов, интересуют простые параметры для мониторинга: доступность, время отклика. (всякая там температура оперативной памяти не нужна). Я так понимаю, zabbix использовать для этого, как гвоздь микроскопом забивать. Что хабровчане посоветуют из опенсорса (желательно написанного на python-е)?
      +1
      Не пугайтесь, заббикс довольно легкий и мало ест в небольших проектах. И там есть встроенный функционал для мониторинга веб-страниц, идеально подойдет www.zabbix.com/documentation/2.0/manual/web_monitoring/example
      А в версии 2.2 вроде как появятся темплейты для создания таких сценариев проверки.
        +1
        > Я так понимаю, zabbix использовать для этого, как гвоздь микроскопом забивать.

        Да нет, вполне можно.
        Для вебсайта кроме собственно доступности и времени респонса на 80-м порту можно еще несколько важных параметров добавить, например:

        — работоспособность каких-то простейших форм на сайте (т.е. задать простой сценарий), что помимо собственно проверки работоспособности, позволит также оценить и скорость работы сайта.
        — доступность БД.
        — LA, память, IO.
          +1
          Тут уже написали про то, что Заббикс не так страшен даже для маленьких проектов. Можно быстро установить готовый образ под Xen, KVM, VMWare, VirtualBox и т.д. Хотя я думаю, что преимуществом изучения Заббикса будет отсутствие рамок и ограничений при росте инфраструктуры.

          Если не Заббикс, то посмотрите на monit. Вполне может подойти для вашего случая.
          +1
          Очень нравятся все эти возможности по скриптам, и гибкости создания шаблонов. Удручает только то, что нужно по мимо шаблона, хранить нужно где-то эти скрипты, и заливать все это на каждый нужный сервер. Уже совсем нет централизации средствами zabbix.
            +1
            Ну так для простых есть же system.run[]
            но конечно если надо большие скрипты то да, здесь уже надо переносить, ну и безопасность никто не отменял

              +1
              Наверное этим должен заниматься configuration management, а не система мониторинга. Централизованное управление скриптами и Заббикс агентами звучит заманчиво до тех пор пока нет зоопарка в версиями OS, устройства более менее однородны и их не так много. Puppet, Chef, CFEngine и ansible не даром едят свой хлеб.

              Кроме того не нужно забывать и о безопасности. Часто мониторинг может достучаться до всех устройств компании и это может стать очень удобным вектором атаки. Это одна из причин почему в Заббиксе есть активные агенты и прописываемые в конфигах UserParameters, хотя кто-то может сказать, что system.run[] удобнее и практичнее.
                0
                system.run[] реально удобен для небольших проверок не требующих прав рута. Но даже в этой ситуации никто не застрахован что можно получить данные просто набрав с компа атакующего
                zabbix_get -s HOST_IP -I SERVER_IP -k system.run[cat /etc/passwd] и т.п.
                Поэтому я ее использовать не собираюсь, хотя узнал о ней только вчера из статьи.
                ну а если уж для пользователя zabbix откыто sudo, и оно не привязано к командам, то тут вообще все просто.
                  0
                  Именно так. Говоря о безопасности необходимо помнить, что агенты и прокси Заббикса могут работать как в пассивном режиме (слушают TCP порт) так и в активном, когда они сами подключаются к серверу либо прокси. Так вот, пассивный прокси может быть полезен в случае DMZ, а активный если мы, например, мониторим удалённый офис через интернет. Зная об этом и правильно выбирая настройки мы можем значительно снизить наши риски связанные с безопасностью.
                    0
                    -I разве не для выбора интерфейса, с которого отправлять запрос?
                      0
                      Покопался и уточняю. В -I указывается, на какой ip zabbix_get биндится при коннекте к агенту.
                      Теоритически — да, можно изменять пакеты, но сработает это только если злоумышленник вклинится между вами и свичом провайдера…
                        0
                        Ну в данном случае я приводил -I больше по привычке.
                        т.к. у меня настроен HA кластер для zabbix с одинм активным ip адреом, на который уже настроены клиенты. Поэтому прихродится всегда указывать данный параметр.
                +1
                Все бы хорошо при таком расширении. У нас таких проверок написано очень много. И вот вчера столкнулся с такой небольшой проблемой. Скрипт проверки написан на scala (только не спрашивайте почему, я ее не знаю) и вроде все нормально, но когда начал создавать элементы данных то они не сразу начинали опрашиваться, писали что заббикс агент не поддерживает. Долго копался в чем же проблема. Потом решил увеличить количество агентов на хосте, и обнаружил что значение Timeout было устновлено в 3 секунды. Конечно я его поднял до максимума, но вот осадок остался. Заббикс бы мог вместо ошибки не «Zabbix agent not support» написать что-нибудь типа «Zabbix agent timeout». Тогда бы ловля блох была бы более продуктивной.
                  +1
                  Полностью согласен, спасибо за напоминание.
                  +3
                  Кстати говоря, где-то начиная с Висты мониторить установку софта много проще не монструозным vbs-скриптом, как в статье, а подпиской на эвенты в эвентлоге Microsoft-Windows-Application-Experience\Program-Inventory, собирая эвенты с номерами: 900 — install in IE, 901 — update in IE, 902 — remove in IE, 903,904 — install, 905 — update, 907,908 — remove.
                  Благо, Заббикс почти из коробки обучен мониторингу эвентлогов.
                    +1
                    Ну скрипт писался с расчетом на Win XP в первую очередь в те далекие (далекие?) времена, когда это была основная операционка в офисах, а заббикс был 1.6 :)
                      0
                      А зачем давать офисным сотрудникам права на установку програм?
                    –2
                    Да, забыл сказать, что автором статьи является wabbit, вот кого надо благодарить.
                      0
                      Товарищи. Я так и не пойму что лучше zabbix или nagios? Может кто нибудь на пальцах минусы и плюсы показать.
                      Да пользуюсь вторым.
                        0
                        Zabbix. Мощнее и более динамично развивается.
                          0
                          Спасибо. Попробуем.
                        0
                        какая-то мистика. Добавляю в userParameter любую команду с grep и результат — рустая строка. Без грепа всё работает.
                          0
                          Готовый шаблон для мониторинга SMART с остальными элементами данных, триггерами прикладываю, так же как и настроенный под него конфиг.

                          Не понял где вы его прикладываете, так что нашел сам вот здесь — github.com/v-zhuravlev/zbx-smartctl,
                          и вот здесь — share.zabbix.com/storage-devices/smart-monitoring-with-smartmontools-lld
                            0
                            А вот если пошли сравнения по диагонали — для небольших офисных сетей (100-200 рабочих станций 10-15 серверов) попробовать Zabbix или Dude?

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое