Проверка доступности сайта каждые N секунд
Проверять доступность сайта считаю занятием ничуть не менее важным, чем своевременное резервное копирование. Есть немало сервисов, позволяющих мониторить доступность сайта с интервалом вплоть до 1 минуты. Но что делать, когда хочется проверять сайт чаще? К примеру, раз в 10 секунд.
Возможна ситуация, когда сервер внезапно подвисает меньше чем на 1 минуту. Обычный мониторинг этого не замечает. А хочется знать точное время, когда такое происходит, чтобы посмотреть по логам, что в этот момент происходило на сервере. Мне не удалось найти такие инструменты, которые бы проверяли сайт чаще, чем раз в минуту. Но на то мне даны навыки кодинга, чтобы написать свой скрипт с блэк-джеком и девушками с пониженной социальной ответственностью.
Кстати, для обычного мониторинга доступности (раз в минуту) я уже много лет использую сервис host-tracker, упоминал его в этой статье. Также он мне бывает полезен, когда лень вспоминать, как настраивать задание в cron на периодический запуск php-скрипта.
Мониторинг доступности на стероидах
Проверять стабильность хостинга, которому вы не доверяете, предлагаю php-скриптом, размещённом на другом хостинге. Хотя бы на другом сервере, особенно хорошо если в другом ДЦ — тогда стремится к нулю вероятность одновременных перебоев на обоих серверах. Но даже если php-скрипт будет проверять свой собственный сервер, то по косвенным уликам можно вычислить время возможного перебоя (например, если время между двумя соседними записями в лог скрипта превышает установленный таймаут).
Для работы нам понадобятся функции:
function bench($fn){
$start = microtime(true);
if ( $r = $fn() ) return $r;
return round( microtime(true) - $start, 5 );
}
function geturl($url, $timeout) {
$curl = curl_init();
$url .= strpos($url, '?') ? '&' : '?';
$url .= 'r='.time().rand();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FORBID_REUSE, true);
curl_setopt($curl, CURLOPT_FRESH_CONNECT, true);
$r = curl_exec($curl);
curl_close($curl);
return $r;
}
bench($fn) измеряет время выполнения куска кода (мы будем измерять время выполнения запроса). geturl($url, $timeout) отправляет get-запрос, ограниченный таймаутом, установленным в параметре $timeout. Чтобы уменьшить вероятность кэширования запроса, к $url я добавляю параметр 'r='.time().rand(). Имейте ввиду, если в работе своего движка используете этот get-параметр.
Также нужна функция для логирования. Можно использовать просто file_put_contents(), но более гибким решением будет написать свою функцию, например, такую:
function tolog($s, $log=LOG) {
$t = explode(".", $_SERVER["REQUEST_TIME_FLOAT"]);
$t = date('d.m H:i:s', $t[0]);
file_put_contents($log, "$t\t$s\n", FILE_APPEND);
}
Здесь предполагается, что имя файла LOG будет задано константой перед вызовом функции.
А теперь сам скрипт мониторинга:
define('ABSPATH', dirname( __FILE__ ) .'/');
define('LOG', ABSPATH .'log.txt'); // лог всех запросов
define('TIMEOUT', 20); // таймаут запроса, секунд
define('LOGLONG', ABSPATH .'loglong.txt'); // лог долгих запросов
define('LONGTIME', 10); // считать запросы долгими, если выполняются дольше N секунд
define('DELAY', 5); // пауза между запросами, секунд
if (trim(file_get_contents('stop'))) {
tolog('stop');
exit;
}
define('URL', 'http://сайт-пациент/checkup.php');
if (! file_exists(LOG)) tolog('start');
// пнём скрипт, если он остановился
if (@ $_GET['wake']) {
if (time()-filemtime(LOG) < TIMEOUT*2) exit;
else tolog('wake');
}
// мониторинг
$benchTime = bench( function() {
$result = geturl(URL, TIMEOUT);
if (! $result) {
$r = 'HTTP timeout';
} else {
$result = explode("\r\n\r\n", $result, 2);
$h = explode("\r\n", $result[0], 2);
$h = $h[0]."\t";
if (strpos($h, '200 OK')) $h = '';
$result = $h.$result[1];
}
tolog($result);
} );
// в отдельный лог долгие ответы
if ($benchTime > LONGTIME) tolog($benchTime, LOGLONG);
sleep(DELAY);
// следующий шаг
$url = 'http://'. $_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];
geturl($url, 2);
Первым делом (на всякий пожарный) в директории скрипта я создаю пустой файл stop. В кодинге случается всякое, особенно под утро. Может получиться так, что скрипт из-за ошибки в логике выполнения начнёт плодить процессы и ддосить собственный сервер. Чтобы при этом рука судорожно не тянулась к кнопке reset, есть файл stop, запись в который значения 1 останавливает безумие скрипта, делая в логе соответствующую пометку.
Как видно, мониторинг сайта осуществляем запросами к скрипту checkup.php на сайте-пациенте. О нём расскажу чуть ниже. Функцией file_exists(LOG) проверяем наличие файла LOG, если отсутствует — создаём. Нужно, чтобы файл существовал, потому что дальше по коду проверяем время его модификации: filemtime(LOG). Дело в том, что никакой хостинг не идеален, и наш проверяющий скрипт может остановиться. Чтобы этого не происходило, скрипт нужно периодически «пинать». Можно добавить ежеминутное задание в cron. Я добавил на host-tracker проверку url monitoring.php?wake=1, и когда вызов скрипта происходит с параметром $_GET['wake'] — значит, осуществляется контроль работы. Тогда скрипт проверяет дату модификации файла LOG, и продолжает работу только в том случае, если лог модифицировался более чем TIMEOUT*2 секунд назад. Иначе делается вывод, что работа скрипта не прекращалась, и дополнительные действия не требуются.
Суть мониторинга в измерении времени выполнения geturl(URL, TIMEOUT). Если запрос выполняется дольше TIMEOUT секунд, curl_exec не дожидается ответа и возвращает false. Тогда в логе делается пометка HTTP timeout. Если ответ пришёл в пределах TIMEOUT секунд, тогда мы имеем заголовки и тело ответа. Меня интересует только первая строка заголовков. Если она не содержит 200 OK, тогда я записываю её в лог. Также меня интересует тело ответа (об этом ниже) — его тоже записываю. Думаю, также имеет смысл где-то рядом с tolog($result) в строковом значении $result поискать HTTP — если найдено, то результат проверки записать также в отдельный лог LOGWRONG. Это вам задание для самостоятельного изучения, если надо.
Далее надеюсь понятно, что в LOGLONG пишем время выполнения долгого запроса $benchTime. Больший интерес вызывает следующий шаг. После паузы в DELAY секунд скрипт вызывает сам себя, чтобы выполнить очередную проверку. Для вызова самого себя я поставил ожидание выполнения запроса 2 секунды. Когда ставил 1 секунду, этого не всегда хватало, и скрипт останавливался каждые несколько часов. Хоть я и предусмотрел функционал «пинка», всё равно не хочется, чтобы скрипт сам по себе отваливался. Можно ещё увеличить это значение, но не стоит делать его больше DELAY. Меня удовлетворило значение в 2 секунды.
Продвинутый читатель должно быть заметил, что получился этакий вариант «phpDaemon для ленивых» без возни с запуском из консоли, без родительских и дочерних процессов. Конечно, слишком грубое сравнение с phpDaemon, но в данном случае для данной задачи простое функциональное программирование позволяет обойтись без создания демонов. Те, кто совсем уж в теме, скажут: «Так это ж пародия на call no wait, почему-то через curl!» Традиционный вариант функции callNoWait через fsockopen:
function callNoWait($url) {
$url = parse_url($url);
$fp = fsockopen(
$url['host'],
isset($url['port']) ? $url['port'] : (
isset($url['scheme']) && $url['scheme'] == 'https' ? '443' : '80'
),
$errno, $errstr, 30
);
if (! $fp) return;
$uri = $url['path'];
if (isset($url['query'])) {
$uri .= '?'.$url['query'];
}
$out = "GET {$uri} HTTP/1.1\r\n"
. "Host: {$parts['host']}\r\n"
. "Connection: Close\r\n\r\n";
fwrite($fp, $out);
// did not read anything
fclose($fp);
}
Только вот на виртуальных хостингах fsockopen часто отключают, потому при необходимости реализовать технологию callNoWait в клиентских проектах (парсер, импорт и т.д.) использую curl.
сайт-пациент/checkup.php
В моём конкретном случае мониторится сайт на WP, для проверки работоспособности сайта написан такой checkup.php:
echo bench(function(){ include 'wp-load.php'; });
Объявление функции bench писал выше. В результате запроса checkup.php в теле ответа возвращается время загрузки основного функционала движка WP.
Disclaimer
На данной странице код приведён as is, это не продакшен. Код намеренно дан кусками, а не в архиве с системными требованиями и инструкцией по установке. Цель — помочь юзеру в реализации, а не выполнить реализацию за него. Юзер несёт ответственность за любые проблемы в продакшене, возникшие в результате использования кода!
По-хорошему, дисклеймер надо писать вначале. Но я решил предупредить только внимательных читателей. Остальные пусть с наскоку делают копипасту а потом хейтят меня в комментах — хоть какой-то фидбэк. Важная информация для внимательных читателей: здесь не реализовано никакой защиты
- от попыток нарушить работу скрипта,
- от недоброжелателей, которые знают, что у вас на сайте есть скрипт
monitoring.phpи как этот скрипт работает, - от поисковых ботов, которые где-то получили ссылку на
monitoring.phpна вашем сайте и пытаются его проиндексировать...
Предлагаю для вызова скрипта как минимум использовать get-параметр salt, в котором передавать секретную строку, и завершать работу скрипта, если salt не верна. Также в вашем распоряжении ограничение по IP, секретные куки и что захотите.

2 ответа к “Проверка доступности сайта каждые N секунд”
Павел, очень интересный код получился у вас, может проще сыпать HEAD запросами, хоть раз в секунду (они не будут особо нагружать тестируемого)
И смотреть 200 ОК ?
Делать выводы по времени ответа не очень, как помне, почему: путь запроса от сервера к серверу каждый раз разный (разбирался тут с работой traceroute — там все не однозначно), как отделить проблемы каналов связи от проблем на сервере.
код для HEAD запроса:
// нужно дописать проверку на 200 ОК, и если нет, отправлять email админу.
$dst_url='http://pavluha.net/'; // create a new cURL resource $ch = curl_init (); // set URL and other appropriate options curl_setopt ($ch, CURLOPT_URL, $dst_url); // This changes the request method to HEAD curl_setopt ($ch, CURLOPT_NOBODY, true); curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT ,5); // connect timeout curl_setopt ($ch, CURLOPT_TIMEOUT, 10); // curl timeout // grab URL and pass it to the browser if (FALSE === curl_exec ($ch)){ echo ('open '.$dst_url.' failed'."\n"); }else{ // Fetch the HTTP-code (cred: @GZipp) $retcode = curl_getinfo ($ch, CURLINFO_HTTP_CODE); echo ('HTTP return code='.$retcode."\n"); } // close cURL resource, and free up system resources curl_close ($ch);PS:
Павел, я вам писал на почту по поводу оптимизации сайта, а в ответ тишина... "Видно он не вернулся из боя..." ©
Напишите пожалуйста по оптимизации моего сайта, со статикой сломал голову, почему не кешируется. И со временем первого ответа сервера не могу понять почему такое большое (800-1300мс)
Михаил, спасибо за проявленный интерес и дельное дополнение. Действительно, для проверки доступность можно использовать HEAD или другой удобный для конкретной задачи метод запроса.
В статье я неточно выразился — под долгими ответами имел ввиду долгое время выполнения (пациент при выполнении бенча измеряет время выполнения и отправляет его в ответе — для получения этого ответа я и использую GET). Таким образом, я не только получаю факт доступности ресурса (200 ОК), но и время выполнения эталонной задачи (например, чтение/запись 1К файлов, выполнение 1К запросов к БД и прочее). При желании, время выполнения каждого теста можно передать отдельным параметром, а также сообщить проверяющему скрипту текущую загрузку CPU, RAM etc.