Пара вопросов от новичка.

47 views
Skip to first unread message

Andrey Deykunov

unread,
Apr 11, 2021, 1:14:37 AM4/11/21
to SObjectizer
Добрый день.
Осваиваю фреймворк и у меня вот какой вопрос. Предположим, что у меня агент имеет дочернюю кооперацию, содержащую набор однотипных агентов. Есть ли возможность добавлять/удалять агентов в этой кооперации? 

И еще вопрос. Предположим что те самые агенты имеют стейты:

state_t off{this}, on{this};
state_t ready{initial_state_of{on}};
state_t doin_some_work{substate_of{on}};

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

Спасибо.

Yauheni Akhotnikau

unread,
Apr 11, 2021, 1:24:30 AM4/11/21
to SObjectizer
Доброго дня!
 
Осваиваю фреймворк и у меня вот какой вопрос. Предположим, что у меня агент имеет дочернюю кооперацию, содержащую набор однотипных агентов. Есть ли возможность добавлять/удалять агентов в этой кооперации? 

Нет, кооперация после регистрации является иммутабельным объектом, поэтому в уже зарегистрированную кооперацию нельзя добавлять новых агентов, нельзя удалять из нее агентов по одному.

Можно сделать, например, так: есть агент Owner, он делает себе дочернюю кооперацию main_children, в которую помещаются агенты, которые должны жить максимально долго. Далее, когда у вас появляется необходимость создать нового агента, то вы создаете его в отдельной кооперации secondary_child_i, которую делаете дочерней для main_children.

Тогда вы имеете возможность дерегистрировать всех дочерних агентов разом: достаточно дерегистрировать main_children. Так же вы можете удалять кооперации secondary_child_i по одной. 

И еще вопрос. Предположим что те самые агенты имеют стейты:

state_t off{this}, on{this};
state_t ready{initial_state_of{on}};
state_t doin_some_work{substate_of{on}};

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

Тут не очень понятно. Вам нужно, чтобы в doing_some_work полностью отработал какой-то event_handler, в самом конце которого есть вызов `ready.activate()`. Агент должен войти в ready, но не ждать следующего входящего сообщения/сигнала, сразу же должен перейти в off. Я так воспринимаю вашу ситуацию.

Если я прав, то мне не понятно, в чем смысл этого промежуточного ready? В том, чтобы сделать какие-то действия в on_enter для ready? 

Спасибо.

Andrey Deykunov

unread,
Apr 11, 2021, 4:33:04 AM4/11/21
to SObjectizer
Евгений,

Спасибо за ответ! Дело вот в чем, я планирую переписать huntgroup модуль нашего hpbx сервера, фактически этот модуль представляет собой коллцентр с сущностями (hungroups) содержащими агентов (agents). Содержимое хантгрупп может меняться, как и сами хантгруппы, поэтому я и спросил насчет удаления/добавления агентов. Возможно, в данном случае было бы выгоднее делать много коопераций с одним агентом в каждой ведь по суть агенты не работают над какой-то совместной задачей, но это было бы накладно с точки зрения производительности, ведь, как я понял регистрация каждой кооперации довольно дорога. 
По поводу второго вопроса, суть в том что любого агента хангруппы в любой момент времени могут разлогинить снаружи (из админки) и если он в этот момент висит на звонке мне нужно чтобы агент закончил этот звонок прежде чем разлогиниться.

Спасибо,
Андрей. 

воскресенье, 11 апреля 2021 г. в 08:24:30 UTC+3, eao...@gmail.com:

Yauheni Akhotnikau

unread,
Apr 11, 2021, 5:00:27 AM4/11/21
to SObjectizer
Спасибо за ответ! Дело вот в чем, я планирую переписать huntgroup модуль нашего hpbx сервера, фактически этот модуль представляет собой коллцентр с сущностями (hungroups) содержащими агентов (agents). Содержимое хантгрупп может меняться, как и сами хантгруппы, поэтому я и спросил насчет удаления/добавления агентов. Возможно, в данном случае было бы выгоднее делать много коопераций с одним агентом в каждой ведь по суть агенты не работают над какой-то совместной задачей, но это было бы накладно с точки зрения производительности, ведь, как я понял регистрация каждой кооперации довольно дорога.

Это зависит от частоты регистраций/дерегистраций. В SO-5.6/5.7 проведена значительная оптимизация в этом месте. И новые кооперации могут регистрироваться с темпом в несколько десятков тысяч штук в секунду. Так что если вы будете создавать новых агентов с темпом в 5K/sec или 10K/sec, то большого негативного влияния это оказать не должно. Но эти показатели, конечно же, зависят от разных параметров.

В SO-5 есть несколько бенчмарков, которые могут показать производительность этих операций на вашей машине и вашем компиляторе:

 
Например, у меня на i5-8265U под Ubuntu-18.04 и GCC-7 бенчмарк coop_dereg показывает такие результаты:

$ ./target/gcc_7_5_0__x86_64_linux_gnu/release/_test.bench.so_5.coop_dereg -c 5000 -a 200 -D thread_pool
Configuration: coops: 5000, agents_per_coop: 200, disp: thread_pool
registrations: 5000, total_time: 1.512s
price: 0.0003024s
throughtput: 3306.878307 registrations/s
pings: 1000000, total_time: 0.251s
price: 2.51e-07s
throughtput: 3984063.745 pings/s
ping-pongs: 1000000, total_time: 0.307s
price: 3.07e-07s
throughtput: 3257328.99 ping-pongs/s
deregistrations: 5001, total_time: 0.744s
price: 0.000148770246s
throughtput: 6721.774194 deregistrations/s

Тут создается, если не ошибаюсь 1M агентов в 5K кооперациях.

Если же агентов в 10 раз меньше, то:

$ ./target/gcc_7_5_0__x86_64_linux_gnu/release/_test.bench.so_5.coop_dereg -c 5000 -a 20 -D thread_pool
Configuration: coops: 5000, agents_per_coop: 20, disp: thread_pool
registrations: 5000, total_time: 0.189s
price: 3.78e-05s
throughtput: 26455.02646 registrations/s
pings: 100000, total_time: 0.029s
price: 2.9e-07s
throughtput: 3448275.862 pings/s
ping-pongs: 100000, total_time: 0.034s
price: 3.4e-07s
throughtput: 2941176.471 ping-pongs/s
deregistrations: 5001, total_time: 0.081s
price: 1.619676065e-05s
throughtput: 61740.74074 deregistrations/s

Опять 1M агентов, то в 50K кооперациях по 20 в каждой:

 ./target/gcc_7_5_0__x86_64_linux_gnu/release/_test.bench.so_5.coop_dereg -c 50000 -a 20 -D thread_pool
Configuration: coops: 50000, agents_per_coop: 20, disp: thread_pool
registrations: 50000, total_time: 1.764s
price: 3.528e-05s
throughtput: 28344.6712 registrations/s
pings: 1000000, total_time: 0.314s
price: 3.14e-07s
throughtput: 3184713.376 pings/s
ping-pongs: 1000000, total_time: 0.37s
price: 3.7e-07s
throughtput: 2702702.703 ping-pongs/s
deregistrations: 50001, total_time: 0.813s
price: 1.625967481e-05s
throughtput: 61501.84502 deregistrations/s

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

 
По поводу второго вопроса, суть в том что любого агента хангруппы в любой момент времени могут разлогинить снаружи (из админки) и если он в этот момент висит на звонке мне нужно чтобы агент закончил этот звонок прежде чем разлогиниться.

Так а как это выражается в какой-то последовательности сообщений? А то я пока не очень понимаю, как действия раскладываются на состояния, event_handler-ы и переходы между состояниями :(
 

Спасибо,
Андрей. 

Andrey Deykunov

unread,
Apr 11, 2021, 5:42:43 AM4/11/21
to SObjectizer
Евгений,

доберусь до компа с моими экспериментами и распишу вам переходы)

Андрей

воскресенье, 11 апреля 2021 г. в 12:00:27 UTC+3, eao...@gmail.com:

Andrey Deykunov

unread,
Apr 12, 2021, 2:12:40 AM4/12/21
to SObjectizer
Евгений,

вот пример тестового агента

int agent_behavior_test()
{
struct sig_cs_start final : public so_5::signal_t
{
};
struct sig_cs_calling final : public so_5::signal_t
{
};
struct sig_cs_established final : public so_5::signal_t
{
};
struct sig_cs_finished final : public so_5::signal_t
{
};
struct sig_cs_terminated final : public so_5::signal_t
{
};
struct sig_ag_login final : public so_5::signal_t
{
};
struct sig_ag_logout final : public so_5::signal_t
{
};
struct sig_ag_ready final : public so_5::signal_t
{
};

class test_agent : public so_5::agent_t
{
so_5::state_t st_ag_login{this, "agent is logged in"};
so_5::state_t st_ag_ready{initial_substate_of{st_ag_login}, "agent is ready"};
so_5::state_t st_ag_calling{substate_of{st_ag_login}, "agent is calling"};
so_5::state_t st_ag_onphone{substate_of{st_ag_login}, "agent is onphone"};
so_5::state_t st_ag_wrapup{substate_of{st_ag_login}, "agent is wrapup"};
so_5::state_t st_ag_busy{substate_of{st_ag_login}, "agent is busy"};
so_5::state_t st_ag_logout{this, "agent is logged out"};

public:
test_agent(
context_t ctx, const so_5::mbox_t &mbox, int account_id) : so_5::agent_t(ctx),
   m_account_id(account_id),
   m_mbox(mbox) {}

void so_define_agent() override
{
this >>= st_ag_login;

so_subscribe(m_mbox)
.in(st_ag_wrapup)
.event(&test_agent::on_sig_ag_ready);

so_subscribe(m_mbox)
.in(st_ag_ready)
.event(&test_agent::on_sig_cs_start);

so_subscribe(m_mbox)
.in(st_ag_ready)
.event(&test_agent::on_sig_cs_calling);

so_subscribe(m_mbox)
.in(st_ag_calling)
.event(&test_agent::on_sig_cs_established);

so_subscribe(m_mbox)
.in(st_ag_onphone)
.in(st_ag_calling)
.event(&test_agent::on_sig_cs_finished);

so_subscribe(m_mbox)
.in(st_ag_ready)
.in(st_ag_wrapup)
.event(&test_agent::on_sig_cs_terminated);

so_subscribe(m_mbox)
.in(st_ag_logout)
.event(&test_agent::on_sig_ag_login);

so_subscribe(m_mbox)
.in(st_ag_login)
.event(&test_agent::on_sig_ag_logout);
}

private:
void on_sig_ag_ready(so_5::mhood_t<sig_ag_ready>)
{
cur_state();
if (incoming_logout) {
this >>= st_ag_logout;
incoming_logout = false;
ag_log() << "agent has been logged out";
} else {
this >>= st_ag_ready;
}
cur_state();
}

void on_sig_cs_start(so_5::mhood_t<sig_cs_start>)
{
ag_log() << "incoming sig_cs_started";
}

void on_sig_cs_calling(so_5::mhood_t<sig_cs_calling>)
{
cur_state();
this >>= st_ag_calling;
cur_state();
}

void on_sig_cs_established(so_5::mhood_t<sig_cs_established>)
{
cur_state();
this >>= st_ag_onphone;
incoming_logout = true;
cur_state();
}

void on_sig_cs_finished(so_5::mhood_t<sig_cs_finished>)
{
cur_state();
if (st_ag_onphone == so_current_state())
{
this >>= st_ag_wrapup;
so_5::send_delayed<sig_ag_ready>(m_mbox, std::chrono::seconds(2));
}
else
{
this >>= st_ag_ready;
}
cur_state();
}

void on_sig_cs_terminated(so_5::mhood_t<sig_cs_terminated>)
{
ag_log() << "incoming sig_cs_terminated";
}

void on_sig_ag_login(so_5::mhood_t<sig_ag_login>)
{
cur_state();
this >>= st_ag_ready;
cur_state();
}

void on_sig_ag_logout(so_5::mhood_t<sig_ag_logout>)
{
cur_state();
ag_log() << "logout event is coming, the agent supposed to be looged out as soon as it become ready";
incoming_logout = true;
cur_state();
}

private:
const int m_account_id;
so_5::mbox_t m_mbox;
bool incoming_logout = false;
};

try
{
so_5::launch([](so_5::environment_t &env) {
logger::create_logger_actor(env);

namespace pool_disp = so_5::disp::adv_thread_pool;

so_5::mbox_t mbox = env.create_mbox();

env.introduce_coop(
pool_disp::make_dispatcher(env, 2).binder(
pool_disp::bind_params_t{}.fifo(pool_disp::fifo_t::individual)),
[&mbox](so_5::coop_t &coop) {
coop.make_agent<test_agent>(mbox, 42);
});

so_5::send<sig_cs_start>(mbox);
so_5::send<sig_cs_calling>(mbox);
so_5::send<sig_ag_logout>(mbox);
so_5::send<sig_cs_established>(mbox);
so_5::send<sig_cs_finished>(mbox);
so_5::send<sig_cs_terminated>(mbox);
});
}
catch (const std::exception &ex)
{
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}


звонок начинается со sig_cs_start и заканчивается sig_cs_terminated. сигнал sig_ag_logout приходит с админки в момент когда агент находится на звонке (calling). он должен запомнить это событие, завершить звонок и только когда будет опять перейдет в состояние ожидания (ready) проверить флаг и разлогиниться. я просто думал что есть способ обойтись без дополнительного флага и как-то через хистори или отложенное событие, я не знаю есть ли такая возможность, отложить обработку logout.

Андрей







воскресенье, 11 апреля 2021 г. в 12:00:27 UTC+3, eao...@gmail.com:
Спасибо за ответ! Дело вот в чем, я планирую переписать huntgroup модуль нашего hpbx сервера, фактически этот модуль представляет собой коллцентр с сущностями (hungroups) содержащими агентов (agents). Содержимое хантгрупп может меняться, как и сами хантгруппы, поэтому я и спросил насчет удаления/добавления агентов. Возможно, в данном случае было бы выгоднее делать много коопераций с одним агентом в каждой ведь по суть агенты не работают над какой-то совместной задачей, но это было бы накладно с точки зрения производительности, ведь, как я понял регистрация каждой кооперации довольно дорога.

Yauheni Akhotnikau

unread,
Apr 12, 2021, 4:33:45 AM4/12/21
to SObjectizer
Спасибо за пример!

Теперь стало понятнее.

Нет, средствами текущей версии SObjectizer подобная логика сейчас не реализуется. Вам нужно делать какие-то собственные флаги внутри агента и проверять их при входе в st_ag_ready.

Если же рассуждать "на будущее", то нужный вам эффект мог быть достигнут, например, следующим образом:

* при обработке sig_ag_logout агент отсылает себе специальный сигнал sig_ag_logout_when_ready;
* этот сигнал поступает в очередь агента, но не обрабатывается до тех пор, пока агент не вернутся в состояние st_ag_ready;
* как только агент возвращается в это состояние SObjectizer извлекает сохраненный экземпляр sig_ag_logout_when_ready и отправляет его на обработку вперед всех других сообщений, которые стоят в очереди агента.

Насколько я помню, в Akka есть подобный механизм. Там он называется Stash (https://doc.akka.io/docs/akka/current/typed/stash.html).
В SObjectizer сейчас его нет. Были мысли его сделать, но для этого требовалось, чтобы такая функциональность кому-то была нужна.

Ваш пример, наверное, всего лишь второй случай, когда такая функциональность кому-то оказывается нужна "в дикой природе", а не в игрушечных примерах.

Andrey Deykunov

unread,
Apr 12, 2021, 5:58:27 AM4/12/21
to SObjectizer
Евгений,

Спасибо за развернутый ответ! Буду дальше разбираться и проектировать архитектуру. 

Андрей

понедельник, 12 апреля 2021 г. в 11:33:45 UTC+3, eao...@gmail.com:
Reply all
Reply to author
Forward
0 new messages