Модераторы: korob2001, ginnie
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Anyevent::HTTP получить время для каждого запроса 
:(
    Опции темы
HeinzFelfe
  Дата 15.12.2012, 15:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 18
Регистрация: 15.9.2012

Репутация: нет
Всего: нет



Здравствуйте.
Возникла необходимость получить время затраченно на посылание запроса и ожидания ответа. 
http_request'ы делаются в цикле, по элементам массива с url-адресами. Т.е. если бы массив был маленький, равный значению $AnyEvent::HTTP::MAX_PER_HOST, то можно было бы просто перед циклом делать вызов time, и потом в обработчике http_request ещё один time, и из последнего вычесть первый.
Но если размер массива много больше $AnyEvent::HTTP::MAX_PER_HOST, то такой пусть кривой, но все таки способо, не сработает.

Не нашёл информации о том как это мжно реализовать.

По моим соображениям, что бы получить эту информацию нужно писать свой вариант tcp_connect
Код

http_request $method => $url, tcp_connect => $callback->($host, $service, $connect_cb, $prepare_cb),  $cb->($data, $headers) {}

что бы непосредственно в нем для каждого соединения вызывать time в начале и в конце его кода.

Но как его писать я честно говоря представляю слабо... 
Да и возможно есть более легкие и очевидные способы получения времени ожидания ответа?
PM MAIL   Вверх
Pfailed
Дата 16.12.2012, 10:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 933
Регистрация: 19.7.2009

Репутация: 22
Всего: 39



Легче использовать опцию on_prepare. Но нужно учесть, что это не будет работать для повторных keepalive соединений. Поэтому persistent устанавливаем в ноль.
Код

for my $url (@urls) {
    my $start_time;
    http_get $url, persistent => 0, on_prepare => sub {
        unless (defined $start_time) {
            # do not reset start_time on redirect
            $start_time = time;
        }
        $timeout; # on_prepare callback should return connect timeout
    }, sub {
        my $total_time = time - $start_time;
    }
}




--------------------
PM MAIL   Вверх
HeinzFelfe
Дата 16.12.2012, 14:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 18
Регистрация: 15.9.2012

Репутация: нет
Всего: нет



Цитата(Pfailed @ 16.12.2012,  10:00)
Легче использовать опцию on_prepare. 

Эта штука работает уже лучше)
Но все равно не корреткно. 
Представим, что у нас в массиве 100 хостов на которые мы должны отправить get-запрос.
Представим, что у нас $AnyEvent::HTTP::MAX_PER_HOST=20, т.е. за один раз отправленно 20 запросов, и для каждого из них $start_time = time; будет один и тот же.
И даже если предложить, что ответ от этих 20 запросов придет оновременно, то вызов колбэка (в котором мы считаем время итоговое):
Код

sub 
{
        my $total_time = time - $start_time;
}
 
для кождого из них будет происходить последовательно, т.е. не сразу 20 одновременных  my $total_time = time - $start_time;. А будет задержка, и при значительных размерах массива, задержка будет так же значительной.
К примеру для массива из 86 адресов, и с заданным таймаутом 4 секунды, для некоторых адресов итоговое время получается 5, 6, 7 а то и все 9 секунд. А такого в принципе быть не должно, ну так как таймаут всего 4 секунды. 

Т.е. пока что, все указывает на то, что подсчет времени должен выпонляться именно в своем варианте tcp_connecte (который нужно самому написать). 
А как его писать я пока-что не разобрался, увы, английский мой оставляет желать лучшего, а все гайды только на нем.

Есть у вас мысли какие-нибудь?
PM MAIL   Вверх
Pfailed
Дата 16.12.2012, 15:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 933
Регистрация: 19.7.2009

Репутация: 22
Всего: 39



Цитата(HeinzFelfe @  16.12.2012,  14:53 Найти цитируемый пост)
К примеру для массива из 86 адресов, и с заданным таймаутом 4 секунды, для некоторых адресов итоговое время получается 5, 6, 7 а то и все 9 секунд. А такого в принципе быть не должно, ну так как таймаут всего 4 секунды. 

Таймаут в 4 секунды не означает, что запрос завершится не позднее, чем через 4 секунды. Посмотрите описание опции timeout, там сказано, что это таймаут на каждую операцию в отдельности. Т.е. если connect() займет 2 секунды, а потом read из сокета еще 3 (а таких вызовов может быть много и у каждого таймаут считается сначала), то запрос не уложится в предполагаемые 4 секунды. В общем случаи таймаут + 10 секунд это норма.
Это больше похоже на причину полученных вами 9 секунд, чем то, что вызов 86 коллбеков занял 5 секунд. Хотя тут конечно нужно смотреть на код этих коллбеков.



--------------------
PM MAIL   Вверх
HeinzFelfe
Дата 16.12.2012, 15:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 18
Регистрация: 15.9.2012

Репутация: нет
Всего: нет



Что бы не быть голословным вот пример кода.
Правда в процессе появилась ещё одна стремная проблема... о ней после.
В 50 строке, в колбэке, я ставлю sleep 4 секунды (равный значению таймаута, или больше него, это для второй проблемы актуально).
Код

#!/usr/bin/perl
use strict;
use AnyEvent::HTTP;
use Time::HiRes qw(time);

$AnyEvent::HTTP::MAX_PER_HOST=10;
my $cv = AnyEvent->condvar;

my $urls = 
  ["http://www.google.com",
    "http://www.ya.ru/",
    "http://www.youtube.com",
    "http://www.baidu.com",
    "http://www.facebook.com",
    "http://www.bing.com",
    "http://www.google.com",
    "http://www.ya.ru/",
    "http://www.youtube.com",
    "http://www.baidu.com",
    "http://www.facebook.com",
    "http://www.bing.com",
    "http://www.google.com",
        ];


my $start = time;


$cv->begin;
for my $url (@$urls) {
    $cv->begin;

    my $start_time;
    my $timeout;
    my $request;  
    
    $request = http_request( GET => $url, persistent => 0, on_prepare => sub 
    {
        unless (defined $start_time) 
      {
            # do not reset start_time on redirect
            $start_time = time;
        }
        $timeout = 4; # on_prepare callback should return connect timeout
    },  sub
    {
        my ($body, $hdr) = @_;
        printf "url: %s, satus: %s, reason: %s \ntime: %s\n\n",$hdr->{'URL'}, $hdr->{'Status'}, $hdr->{'Reason'}, (time - $start_time);
        undef $request;
        sleep 5;
        $cv->end;
      }
   );


  }

$cv->end;
$cv->recv;
print "Total elapsed time: ", time-$start, "ms\n";


И в результате выполнения получаем вот такие значения времени
Код

url: http://www.ya.ru/, satus: 200, reason: Ok 
time: 0.0274899005889893

url: http://www.youtube.com, satus: 595, reason: Время ожидания соединения истекло 
time: 5.03239107131958

url: http://www.baidu.com, satus: 595, reason: Время ожидания соединения истекло 
time: 10.045089006424

url: http://www.facebook.com, satus: 595, reason: Время ожидания соединения истекло 
time: 15.0488760471344

url: http://www.ya.ru/, satus: 200, reason: Ok 
time: 0.0297520160675049


Т.е. видно что узкое место тут это время выпонения колбэка нашего основного, т.е. процедура подсчета времени должна быть сделана до его вызова.

А теперь о второй проблеме...
Как только я постаавил sleep, то вывод скрипта с вот такого приятного и правильного
Код

url: http://www.ya.ru/, satus: 200, reason: Ok 
time: 0.022367000579834

url: http://www.ya.ru/, satus: 200, reason: Ok 
time: 0.0325219631195068

url: http://www.google.ru/, satus: 200, reason: OK 
time: 0.219401121139526

url: http://www.google.ru/, satus: 200, reason: OK 
time: 0.169860124588013

url: http://www.bing.com, satus: 200, reason: OK 
time: 0.215709924697876

url: http://www.google.ru/, satus: 200, reason: OK 
time: 0.155948877334595

Сменился сплошными ошибками
Код

url: http://www.ya.ru/, satus: 200, reason: Ok 
time: 0.0274899005889893

url: http://www.youtube.com, satus: 595, reason: Время ожидания соединения истекло 
time: 5.03239107131958

url: http://www.baidu.com, satus: 595, reason: Время ожидания соединения истекло 
time: 10.045089006424

url: http://www.facebook.com, satus: 595, reason: Время ожидания соединения истекло 
time: 15.0488760471344

url: http://www.ya.ru/, satus: 200, reason: Ok 
time: 0.0297520160675049


Т.е. получается... пока один колбэк выполлняется, в это время в фоне висят соединения, для которых тикает тайм-аут, и пока выполняется колбэк, таймаут может истечь, и мы получим такую вот ерунду 595, reason: Время ожидания соединения истекло...
Эээээ, а как тогда быть?! Где тогда задавать тайм-аут, что бы он был у каждого соединения своим?
Или я что-то не так понял?

Это сообщение отредактировал(а) HeinzFelfe - 16.12.2012, 15:35
PM MAIL   Вверх
Pfailed
Дата 16.12.2012, 16:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 933
Регистрация: 19.7.2009

Репутация: 22
Всего: 39



Любой sleep губителен для событийно-ориентированной программы. Коллбек должен отрабатывать максимально быстро, т.к. все оперции выполняются в рамках одного процесса и потока. Пока вы делаете sleep вся программа стопорится.
Кстати, в вашем примере отсутствует опция timeout, а таймаут по умолчанию 5 минут.


Цитата(HeinzFelfe @  16.12.2012,  15:32 Найти цитируемый пост)
Эээээ, а как тогда быть?! Где тогда задавать тайм-аут, что бы он был у каждого соединения своим?

Опция timeout и задает таймаут для каждого соединения, просто не нужно писать неправильные коллбеки, работающие слишком долго.


--------------------
PM MAIL   Вверх
HeinzFelfe
Дата 16.12.2012, 16:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 18
Регистрация: 15.9.2012

Репутация: нет
Всего: нет



Цитата

Кстати, в вашем примере отсутствует опция timeout, а таймаут по умолчанию 5 минут.

Так вроде же колбэк on_prepare должен возвращать таймаут? Он у меня его и возвращает.

Цитата

просто не нужно писать неправильные коллбеки, работающие слишком долго.

Хм... А как тогда быть с задачей когда в коллбеке необходимо обрабатывать или проверять данные, например:
Мне нужно узнать, доступен ли некий ресурс (пусть google.com) из некоторой страны. Я нахожу несколько прокси-серверов для этой страны (но большинство прокси лежащих в отркытом доступе - штуки ненадежные и маложивущие, т.е. чем больше список прокси-серверов тем лучше). Пусть у меня есть 100 прокси Гандураса.
И вот я посылаю запросы на google.com через каждый прокси (в цикле все это дело реализую).

При получении кода ответа 200 мне ненужно ждать ответы от остальных запросов, т.к. на очереди у меня могут стоятть ещё 100 стран,.

Допустим от одного из прокси-сервером мне приходит ответ 200, но прокси эта такая штука ненадежная, что они могут прислать ответ 200 и от несуществующего ресурса, и доверять таким прокси-серверам нельзя. Возникает ситуация, когда нужно сделать проверку (о её устройстве и принципах умолчим). Она занимает существенное время, большее таймаута. И если проверка не пройдена, то нужно выполнять следующий запрос, а тайм-аут к этому времени уже помер, и начинаются чудеса с ошибкой 595.

Как быть в такой ситуации? 

Придется сначала получить ответы от всех 100 запросов, сохранить в переменной адреса прокси для которых пришел ответ 200, и уже после выполнения всех запросов вне http_request'а и его колбэка начинать проверять этот список? Но это будет много дольше. Т.к. задержка между Россией и Гонудрасом может быть большой, да ещё и прокси-загружен - одного ответа можно ждать и 10 и 20 секунд и больше, а для нескольких стран, по 100 прокси в каждой, мы уже полчуаем неплохие задержки по времени.

Или деваться некуда и придется жертвовать скоростью выполнения?

Это сообщение отредактировал(а) HeinzFelfe - 16.12.2012, 16:23
PM MAIL   Вверх
Pfailed
Дата 16.12.2012, 16:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 933
Регистрация: 19.7.2009

Репутация: 22
Всего: 39



Цитата(HeinzFelfe @  16.12.2012,  16:22 Найти цитируемый пост)
Так вроде же колбэк on_prepare должен возвращать таймаут? Он у меня его и возвращает.

on_prepare возвращает таймаут только для операции connect()


Цитата(HeinzFelfe @  16.12.2012,  16:22 Найти цитируемый пост)
 Возникает ситуация, когда нужно сделать проверку (о её устройстве и принципах умолчим). Она занимает существенное время, большее таймаута. И если проверка не пройдена, то нужно выполнять следующий запрос, а тайм-аут к этому времени уже помер, и начинаются чудеса с ошибкой 595.

Всё зависит от секретной проверки. Скорее всего ее тоже можно сделать через anyevent и тогда задача решена. Если вдруг нет, то можно форкать для этой проверки отдельный процесс.



--------------------
PM MAIL   Вверх
HeinzFelfe
Дата 19.12.2012, 02:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 18
Регистрация: 15.9.2012

Репутация: нет
Всего: нет



Касаемо проблемы со странными ошибками 595 и 596.

Как я и думал проблема оказалась не в якобы тяжелых колбэках, которые якобы не могут быть очень сложными, и должны выполняться быстренько (смысл в них тогда вообще...). Можно хоть 100 sleep'ов вставить по 100 секунд, все равно все будет работать так как надо.

Дело оказалось вот в чем.
На моем примере расскажу, для тех кто, вдруг, с подобным столкнется.
Пусть у нас есть список ресурсов ya.ru, google.com, bing.com, и есть список прокси-серверов, например, из 100 штук.
Задача состоит в том, что бы для каждого ресурса определить доступен он хотя бы через один из этих прокси, или нет.

И так у нас есть функция sub доступен_ли_ресурс. Вызываем её, передав ей в качестве параметров первый ресурс ya.ru и массив из проксей. 
В нутри процедуры в цикле по значениям массива с прокси начинаем http_request'ами слать запросы через каждый прокси, как только нам придет ответ 200, нужно завершить процедуру, недожидаясь остальных ответов.

Следующий шагом мы опять вызуываем sub доступен_ли_ресурс в качестве параметров - следующий ресурс  google.com, и снова массив из 100 прокси. 
Начинам делать запросы и тут... вылезают всякие гадости с кодом ошибки 595 или 596. А проблема вот в чем...
Когда мы досрочно завершаем первый вызов функции, то внутри тех tcp_connect'ов, ответа от которых мы не дождались остался жив таймер, который задается значением таймаута. Подробности в файле socket.pm библиотеки anyevent::socket, кратко приведу код из неё:

my $timeout = $prepare && $prepare->($state{fh});

         $timeout ||= 30 if AnyEvent::WIN32;

         $state{to} = AE::timer $timeout, 0, sub {
            $! = Errno::ETIMEDOUT;
            $state{next}();
         } if $timeout;

         # now connect
         if (
            (connect $state{fh}, $sockaddr)
            || ($! == Errno::EINPROGRESS # POSIX
                || $! == Errno::EWOULDBLOCK
                # WSAEINPROGRESS intentionally not checked - it means something else entirely
                || $! == AnyEvent::Util::WSAEINVAL # not convinced, but doesn't hurt
                || $! == AnyEvent::Util::WSAEWOULDBLOCK)
         ) {
            $state{ww} = AE::io $state{fh}, 1, sub {
               # we are connected, or maybe there was an error
               if (my $sin = getpeername $state{fh}) {
                  my ($port, $host) = unpack_sockaddr $sin;

                  delete $state{ww}; delete $state{to};

                  my $guard = guard { %state = () };

                  $connect->(delete $state{fh}, format_address $host, $port, sub {
                     $guard->cancel;
                     $state{next}();
                  });
               } else {
                  if ($! == Errno::ENOTCONN) {
                     # dummy read to fetch real error code if !cygwin
                     sysread $state{fh}, my $buf, 1;

                     # cygwin 1.5 continously reports "ready' but never delivers
                     # an error with getpeername or sysread.
                     # cygwin 1.7 only reports readyness *once*, but is otherwise
                     # the same, which is actually more broken.
                     # Work around both by using unportable SO_ERROR for cygwin.
                     $! = (unpack "l", getsockopt $state{fh}, Socket::SOL_SOCKET(), Socket::SO_ERROR()) || Errno::EAGAIN
                        if AnyEvent::CYGWIN && $! == Errno::EAGAIN;
                  }

                  return if $! == Errno::EAGAIN; # skip spurious wake-ups

                  delete $state{ww}; delete $state{to};

                  $state{next}();


Красным отмечена инициализация тайм-аута и таймера связанного с ним.
Очевидно, что если мы не будем ждать осталных ответов, то вот эта строчка (а их там две таких фиолетовеньких, перва сбрасывает таймаут когда соеденинее прошло хорошо, а вторая когда случилась какая-нибудь ошибка)  
Код

delete $state{ww}; delete $state{to};
 просто не будет выполнена. И при следующих вызовах tcp_connect, случится какая-то билеберда (полностью механимз этой аварии я не понял, да оно и не к чему на этом этапе мне) с инициализацией таймера, итогом которой будут ошибки 595 или 596. Как я понял там возникает ситуация когда таймер уже весь кончился, И вот как раз пока он ставиться заново, в этот самый миг с мертым таймером успевают выполнится N http_request'ов, и естественно они выполнятся неправильно, так как ни какого тайм-аута нету, он весь вышел, а новый установиться не успел. И как только он установиться занов все снова заработает. Правда к этому моменту из 100 запросов, штучек 40 будут уже выполнены с кодом 595 или 596.

Для решения этой проблемы я вижу два решения.
1. Крутое. Написать свой tcp_connect со всеми плюшками и обработчиками всяких нехороших ситуаций.
2. Простое. После вызова такой функцииsub доступен_ли_ресурс, вставлять функцию sub ловушка_для_не_прибитого_таймера. Которая будет выглядеть примерно так:
Код

sub ловушка_для_не_прибитого_таймера ()
{    
    my $cv = AnyEvent->condvar;
    http_head 'http://127.0.0.1/',  timeout => 1,  sub  
   {    
       print "дальше все будет ок\n";
       $cv->send();    
   };    
 
   $cv->recv;


использование http://127.0.0.1/ это на всякий случай, вдруг наша первая функция sub доступен_ли_ресурс отработает вся полностью, и с таймаутами там все будет хорошо, и что бы не стопариться на ожидании ответа от какого-нибудь ресурса лишних милисекунд мы постучимся к себе localhost.

Так вот эта функция на себе испытает всю прелесть криво завершенных tcp_connect'ов из передыдущей функции, кое-как отрботает быстренько, нормально завершит tcp_connect и прибьет таймер как надо. И уже следующий вызов многострадальной sub доступен_ли_ресурс, пройдет как по маслу.

Решение костыльное, но рабочее и времени на свое выполнение много не занимающее.
Но в идеале, конечно же, свой tcp_connect.


За некоторый сумбур в изложении и наличее очепяток извиняюсь.

P.S.
 Учите английский, товарищи, и решение подобных проблем займет у вас куда меньше веремени чем у меня, с моими куцими знаниями...

Это сообщение отредактировал(а) HeinzFelfe - 19.12.2012, 02:16
PM MAIL   Вверх
Pfailed
Дата 19.12.2012, 19:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 933
Регистрация: 19.7.2009

Репутация: 22
Всего: 39



Цитата(HeinzFelfe @  19.12.2012,  02:15 Найти цитируемый пост)
Как я и думал проблема оказалась не в якобы тяжелых колбэках, которые якобы не могут быть очень сложными, и должны выполняться быстренько (смысл в них тогда вообще...). Можно хоть 100 sleep'ов вставить по 100 секунд, все равно все будет работать так как надо.


Как в таком случае объясните результат выполнения этого кода
Код

use AnyEvent::HTTP;
use strict;

my $cv = AnyEvent->condvar;

for my $url (qw!http://www.google.ru/ http://www.yandex.ru/ http://www.mail.ru/!) {
    http_get $url, timeout => 5, sub {
        warn $url, " done with " . $_[1]->{Status};
        sleep 10;
    }
}

$cv->recv;


Цитата

http://www.google.ru/ done with 200 at t.pl line 8.
http://www.yandex.ru/ done with 597 at t.pl line 8.
http://www.mail.ru/ done with 597 at t.pl line 8.





--------------------
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Perl"
korob2001
sharq
  • В этом разделе обсуждаются общие вопросы по языку Perl
  • Если ваш вопрос относится к системному программированию, задавайте его здесь
  • Если ваш вопрос относится к CGI программированию, задавайте его здесь
  • Интерпретатор Perl можно скачать здесь ActiveState, O'REILLY, The source for Perl
  • Справочное руководство "Установка perl-модулей", можно скачать здесь


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, korob2001, sharq.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Perl: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1276 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.