이벤트

아이펀 엔진은 event-driven 방식의 framework 입니다. 다시 말해 아이펀 엔진을 이용하여 작성하는 대부분의 코드는 이벤트로서 실행됩니다.

이 챕터는 이벤트의 작동 방식에 대해서 설명합니다.

이벤트란?

이벤트는 크게 엔진 이벤트와 사용자 정의 이벤트로 구분됩니다. 엔진 이벤트의 경우 네트워크, 타이머 등 엔진 내부 이벤트가 발생될 때 실행될 핸들러를 등록해서 활용합니다. (각각, 세션으로부터 패킷 수신하기(수신)타이머 를 참고해주세요.) 사용자 이벤트는 Event::Invoke() 를 이용해 이벤트를 생성합니다.

이벤트는 하나 이상의 이벤트 쓰레드에 의해 처리됩니다. 각 이벤트는 이벤트 실행순서와 이벤트 태그 에 설명된 대로 태그 를 부여 받는데, 같은 태그의 이벤트들은 순차 처리가 보장되며, 서로 다른 태그의 이벤트들은 병렬처리됩니다.

한 이벤트 안에서 일어나는 ORM 관련 object 변경 사항들은 이벤트 함수 종료시 하나의 트랜잭션으로 배치 처리됩니다. (자세한 내용은 ORM Part 1: Overview트랜잭션 을 참고해주세요.)

이벤트 종류

엔진 이벤트

엔진에 발생시켜주는 이벤트로서, network message handler, timer handler, 그리고 엔진 각 기능의 콜백들(Leaderboard, Authenticator, Biller, HttpClient, Reids 등) 이 이에 해당합니다.

각 이벤트 별로 핸들러나 콜백을 등록해 두면 엔진이 필요할 때 해당 함수를 실행시킵니다.

Important

Rpc::Call()Mariadb::ExecuteQuery()callback 은 이벤트에서 제외됩니다.

사용자 정의 이벤트

필요할 때 언제든 Event::Invoke() 를 이용해 이벤트를 생성할 수 있습니다. 이 함수들에 인자로 전달된 handler 는, 엔진 이벤트와 마찬가지로 이벤트 쓰레드에 의해 호출되는 방식으로 동작합니다. 사용자 정의 이벤트는 개발 편의를 위해서나 성능 향상을 위해서 사용됩니다.

Event::Invoke() 함수의 인자로 이벤트로 실행할 함수를 전달하면 해당 함수는 이벤트로 실행됩니다.

#include <funapi.h>

void event_function_a() {
  LOG(INFO) << "event_function_a";
  ...
}

void event_function_b(int param1, string param2) {
  LOG(INFO) << "event_function_b";
  ...
}

void some_function() {
  ...
  // 예제 1
  Event::Invoke(&event_function_a);
  ...
  // 예제 2
  // boost::bind 를 참고 하시기 바랍니다.
  Event::Invoke(bind(&event_function_b, 1234, "hello?"));
  ...

  // 예제 3
  // C++ 1x 를 사용하면 아래처럼 lambda 함수를 이용할 수 있습니다.
  auto event_function_c = []() {
    LOG(INFO) << "event_function_c";
    ...;
  };
  Event::Invoke(event_function_c);

  // 예제 4
  // lambda 함수는 아래처럼 표현할수 도 있습니다.
  Event::Invoke([]() {
    LOG(INFO) << "event_function_d";
    ...;
  });
}

Event.Invoke() 함수의 인자로 이벤트로 실행할 함수를 전달하면 해당 함수는 이벤트로 실행됩니다.

using funapi;

...

public void EventFunctionA()
{
  Log.Info ("EventFunctionA");
}

public void EventFunctionB(int param1, string param2)
{
  Log.Info ("EventFunctionB");
}

public void SomeFunction()
{
  ...
  // 예제 1
  Event.Invoke (EventFunctionA);

  // 예제 2
  // lambda 함수를 이용하는 방법입니다.
  Event.Invoke (() => {
    int integer_value = 1;
    string hello_string = "hello";
    EventFunctionB(integer_value, hello_string);
  });

  // 예제 3
  // delegate를 이용하여 lambda 함수를 이용할 수 있습니다.
  Event.EventFunction lambda_function = () => {
    Log.Info ("lambda function");
  };
  Event.Invoke (lambda_function);
}

이벤트 실행순서와 이벤트 태그

Event 는 기본적으로 발생한 순서대로 큐에 들어갑니다. 하지만 이벤트를 담당하는 쓰레드가 대개 1개 이상이기 때문에 이벤트의 실행은 병렬적으로 일어납니다. (이벤트 쓰레드 개수 조정에 대해서는 이벤트 기능 설정 파라미터 을 참고하세요.)

이는 이벤트 처리 순서가 보장되어야 되는 환경에서는 곤란할 수 있습니다. 그때문에 아이펀 엔진은 처리 순서가 보장되어야 되는 이벤트들을 같은 태그로 묶는 기능을 지원합니다. 이벤트에 태그를 지정하면 같은 tag 의 event 들은 순서가 보장되고, 다른 tag 의 이벤트들은 병렬적으로 실행됩니다.

예를 들어 네트워크 이벤트는 자동으로 session 별로 독립적인 tag 가 사용되어 session 단위에서 message 처리 순서를 보장하지만, 다른 session 과는 병렬적으로 동작하게 됩니다.

아래 코드는 event 1event 2tag1 으로, event 3event 4tag2 로 지정된 경우입니다.

  • event 1 은 항상 event 2 보다 먼저 실행됩니다.

  • event 3 은 항상 event 4 보다 먼저 실행됩니다.

  • 하지만 event 1 + event 2event 3 + event 4 는 병렬적으로 처리되기에 어떤 것이 먼저 실행될지 알 수 없습니다.

#include <funapi.h>

EventTag my_tag_1 = RandomGenerator::GenerateUuid();
EventTag my_tag_2 = RandomGenerator::GenerateUuid();

function<void(string)> my_event = [](string event_msg) {
  LOG(INFO) << event_msg;
};

// tag 1
Event::Invoke(bind(my_event, "event1"), my_tag_1); // event 1
Event::Invoke(bind(my_event, "event2"), my_tag_1); // event 2

// tag 2
Event::Invoke(bind(my_event, "event3"), my_tag_2); // event 3
Event::Invoke(bind(my_event, "event4"), my_tag_2); // event 4
using funapi;


public void MyEvent(string event_msg)
{
  Log.Info ("my_event called. " + event_msg);
};

public void Example()
{
  System.Guid my_tag_1 = RandomGenerator.GenerateUuid();
  System.Guid my_tag_2 = RandomGenerator.GenerateUuid();

  // tag 1
  Event.Invoke (() => { MyEvent ("event1"); }, my_tag_1);
  Event.Invoke (() => { MyEvent ("event2"); }, my_tag_1);

  // tag 2
  Event.Invoke (() => { MyEvent ("event3"); }, my_tag_2);
  Event.Invoke (() => { MyEvent ("event4"); }, my_tag_2);
}

엔진 이벤트들의 태그

네트워크: 각 네트워크 세션은 session id 값을 이벤트 태그로 하여 실행됩니다. 따라서 각 세션들은 독립이며, 하나의 세션 안에서는 순서가 보장됩니다.

타이머: 모두 하나의 이벤트 태그로 실행됩니다. 따라서 타이머 핸들러들은 시간순으로 호출되는 것이 보장됩니다.

기타: 그 외 엔진의 핸들러나 콜백은 무작위로 생성된 태그로 병렬적으로 실행됩니다.

이벤트 태그 자동 부여 규칙

Event::Invoke() 로 이벤트를 실행시킬 때 이벤트 태그 인자를 생략하면 엔진이 태그를 할당합니다. 이 때, 어디서 이벤트가 생성되었느냐에 따라 다음 규칙을 적용합니다.

이벤트 처리 중에 새 이벤트 생성시

이벤트 처리 중에 새로운 이벤트를 만들면 실행되고 있는 이벤트의 태그를 상속 받습니다. 예를 들어, 네트워크 메시지 핸들러 안에서 새 이벤트를 만들 경우 네트워크 메시지 핸들러의 태그를 상속 받습니다. 앞서 네트워크의 경우 이벤트 태그로 세션 아이디를 쓴다고 했으므로, 결국 세션 아이디가 새 이벤트의 태그가 됩니다.

Important

타이머, API 서비스 핸들러 안에서 새 이벤트 생성시

앞서 이벤트 처리 중 새 이벤트를 만들면, 처리 중이던 이벤트 태그를 상속 받는다고 했습니다. 그러나 예외적으로 타이머 , 서버 관리 Part 1: RESTful APIs 추가 핸들러는 랜덤한 이벤트 태그를 가진 상태로 호출됩니다.

타이머 , 서버 관리 Part 1: RESTful APIs 추가 이벤트에서 사용하는 핸들러는 모두 같은 태그를 상속하는데, 만약 타이머 , 서버 관리 Part 1: RESTful APIs 추가 이벤트가 태그를 상속할 경우 타이머나 API 서비스에서 실행되는 이벤트들과 파생된 이벤트들이 모두 직렬화( serialization ) 되는 문제가 발생하기 때문입니다.

물론 타이머 핸들러 안에서 파생된 이벤트가 새로운 타이머를 생성한 경우 타이머에 해당하는 고정 태그가 부여됩니다.

아래 예제 코드의 4 개 이벤트 함수는 모두 네트워크 이벤트의 태그 값, 즉 해당 메시지를 수신한 session 의 session id 를 상속 받습니다. 따라서 OnMessage -> MessageProcess1 -> MessageProcess2 -> MessageProcess3 의 처리 순서가 보장됩니다.

// 아래 OnMessage 함수는 서버 Install 함수에서
// HandlerRegistry::Register(..., OnMessage) 로 등록된 메시지 핸들러
void OnMessage(const Ptr<Session> &session, const Json &message) {
  ...
  // 이벤트 태그 인자 생략
  Event::Invoke(bind(MessageProcess1, session, message));
  Event::Invoke(bind(MessageProcess2, session, message));
}

void MessageProcess1(const Ptr<Session> &session, const Json &message) {
  // 이 이벤트의 이벤트 태그는 session->id() 와 같습니다.
  ...
  // 이벤트 태그 인자 생략
  Event::Invoke(bind(MessageProcess3, session, message));
}

void MessageProcess2(const Ptr<Session> &session, const Json &message) {
  // 이 이벤트의 이벤트 태그는 session->id() 와 같습니다.
  ...
}

void MessageProcess3(const Ptr<Session> &session, const Json &message) {
  // 이 이벤트의 이벤트 태그는 session->id() 와 같습니다.
  ...
}
// 아래 OnMessage 함수는 서버 Install 함수에서
// HandlerRegistry.Register(..., OnMessage) 로 등록된 메시지 핸들러
public static void OnMessage(Session session, JObject message)
{
  ...
  // 이벤트 태그 인자 생략
  Event.Invoke (() => {
    MessageProcess1 (session, message);
  });
  Event.Invoke (() => {
    MessageProcess2 (session, message);
  });
}

public static void MessageProcess1(Session session, JObject message)
{
  // 이 이벤트의 이벤트 태그는 session.Id 와 같습니다.
  ...
  // 이벤트 태그 인자 생략
  Event.Invoke (() => {
    MessageProcess3 (session, message);
  });
}

public static void MessageProcess2(Session session, JObject message)
{
  // 이 이벤트의 이벤트 태그는 session.Id 와 같습니다.
  ...
}

public static void MessageProcess3(Session session, JObject message)
{
  // 이 이벤트의 이벤트 태그는 session.Id 와 같습니다.
  ...
}

이벤트 핸들러가 아닌 곳에서 새 이벤트 생성시

이 경우는 랜덤하게 생성된 이벤트 태그를 부여합니다. 앞서 설명한 것처럼 랜덤한 이벤트 태그는 이벤트들을 병렬 처리 할 수 있음을 의미합니다.

Tip

이벤트가 아닌 경우는 서버의 Install 또는 Start 함수, Rpc::Call()Mariadb::ExecuteQuery()callback 또는 사용자가 임의로 생성한 스레드에서 실행되는 코드 등 이 있습니다.

아래 예제는 이벤트가 아닌 곳에서 100 개의 이벤트를 만들기 때문에, 랜덤하게 생성된 이벤트 태그를 부여 받는 경우입니다. 각 이벤트가 랜덤한 이벤트 태그를 갖기 때문에 병렬적으로 처리되며 순서가 보장되지 않습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 아래 Start 함수는 엔진이 초기화를 위해 Install 함수 다음에 불러주는
// 함수이며 이벤트가 아닙니다.
bool Start() {
  for (size_t i = 0; i < 100; ++i) {
    auto my_event = [i]() {
      LOG(INFO) << i;
    };

    Event::Invoke(my_event);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
delegate void MyEvent(int idx);

...

bool Start()
{
  MyEvent my_event = (int number) => {
    Log.Info (number.ToString());
  };

  for (int i = 0; i < 100; ++i) {
    int idx = i;
    Event.Invoke (() => {
      my_event(idx);
    });
  }
}

(예시) 이벤트 태그를 활용한 직렬화 기법

같은 이벤트 태그의 이벤트들 사이에서는 순서가 보장되기 때문에 Lock 없이 처리를 직렬화할 수 있습니다. 이를 이용하면 lock 사용을 최소화 하여 서버 성능을 높이는 것이 가능합니다. 보다 자세한 내용은 Cookbook 1: 방 기반 MO 게임 제작 을 참고하세요.

예제 코드는 전역 변수를 업데이트하는 코드입니다. 여러 스레드에서 하나의 값을 변경하는 것은 오류를 만들기 때문에 이벤트 태그를 이용하여 순차적으로 처리되도록 할 수 있습니다.

// 전역변수
int64_t the_counter = 0;
EventTag the_counter_tag;

// 전역변수 초기화(서버 Start 함수)
bool Start() {
  // the_counter 변수 접근을 위한 이벤트 태그를 무작위로 하나 생성
  the_counter_tag = RandomGenerator::GenerateUuid();
  return true;
}

void UpdateCounter() {
  // ++ 연산은 atomic 하지 않아 여러 스레드에서 동시에 수행되면
  // the_counter 값은 기대한 값 보다 작은 값을 갖는 문제가 발생됩니다.
  ++the_counter;
}

// OnXXX 는 어느 곳에서든 불린다고 가정
void OnXXX(...) {
  // the_counter_tag 를 이벤트 태그로 지정
  Event::Invoke(UpdateCounter, the_counter_tag);
}

예제 코드는 클래스 내의 변수를 업데이트하는 코드입니다. 여러 스레드에서 하나의 값을 변경하는 것은 오류를 만들기 때문에 이벤트 태그를 이용하여 순차적으로 처리되도록 할 수 있습니다.

public class SomeClass
{
  static int TheCounter = 0;
  static System.Guid TheCounterTag;

  bool Start()
  {
    // TheCounter 변수 접근을 위한 이벤트 태그를 무작위로 하나 생성
    SomeClass.TheCounterTag = RandomGenerator.GenerateUuid ();
    return true;
  }

  void UpdateCounter()
  {
    // ++ 연산은 atomic 하지 않아 여러 스레드에서 동시에 수행되면
    // TheCounter 값은 기대한 값 보다 작은 값을 갖는 문제가 발생됩니다.
    SomeClass.TheCounter++;
  }

  // OnXXX 는 어느 곳에서든 불린다고 가정
  void OnXXX()
  {
    // TheCounterTag 를 이벤트 태그로 지정
    Event.Invoke (UpdateCounter, SomeClass.TheCounterTag);
  }
}

이벤트 프로파일링 및 디버깅

아이펀 엔진은 이벤트 최적화 및 디버깅을 위한 편리한 기능들을 제공합니다.

Note

CPU 성능 분석에 대한 내용은 CPU 프로파일링 을 참고하시기 바랍니다.

이벤트에 디버깅용 이름 부여

아래 함수를 이용하여 이벤트 이름을 설정할 수 있습니다. 이벤트에 이름을 부여하면 로그를 출력할 때, 이벤트 이름이 같이 출력되어 디버깅시 편리합니다.

Tip

네트워크 이벤트의 경우 엔진이 메시지(패킷)의 타입을 이용하여 자동으로 이름을 부여하기 때문에 생략할 수 있습니다.

SetEventName()
Event.SetEventName()

아래 예제와 같이 이벤트로 실행될 함수의 가장 첫 줄에서 이름을 주는 게 좋습니다.

// invoke 함수로 실행되는 사용자 정의 이벤트
void my_event1() {
  SetEventName("my_event1");
  ...
}

// invoke 함수로 실행되는 사용자 정의 이벤트
auto my_event2 = []() {
  SetEventName("my_event2");
  ...
};

// timer event
void OnTimer(const Timer::Id &, const WallClock::Value &) {
  SetEventName("my timer event");
  ...
};

// 또는 컴파일러 매크로를 이용하여 아래와 같이 할 수도 있습니다.
void my_event3() {
  SetEventName(__func__);
  ...
}
delegate void MyEvent();

// invoke 함수로 실행되는 사용자 정의 이벤트
void MyEvent1()
{
  Event.SetEventName ("my_event1");
  ...
}

Event.EventFunction my_event2 = () => {
  Event.SetEventName ("my_event2");
  ...
};

Event::Invoke 를 이용하여 함수를 이벤트로 실행할 때는 아래처럼 이름을 지어줄 수 있습니다.

Event::Invoke(my_event, "my_event1");
Event.Invoke (MyEvent1, "my_event1");

이름이 있는 이벤트 함수에서 Event::Invoke 함수를 이용하여 새 이벤트를 만들면 기본적으로 "{부모이벤트의이름}_#{생성순서}" 를 이름으로 갖습니다. 예를 들어 이름이 "my_event1" 인 이벤트 함수에서 Event::Invoke 를 3 번 호출했다면 3 번째 이벤트의 이름은 "my_event1_#3" 이 됩니다.

Important

이후에 설명되는 기능들은 모두 이벤트에 이름을 주어야 사용 가능합니다.

병목 이벤트 감지

이벤트를 처리하는데 이벤트 기능 설정 파라미터slow_event_log_threshold_in_ms 보다 오래 걸리면, 아래와 같은 경고 로그가 출력됩니다. 이 로그가 나올 때에는 해당 이벤트 핸들러의 구현을 점검해보시는 것이 좋습니다.

Slow event: event_name={이벤트이름}, event_id={이벤트아이디}, execution_time={처리시간}

심각한 병목 이벤트 강제 취소

일부 이벤트가 전체 시스템을 느리게 만드는 것을 방지하기 위해서, 이벤트를 처리하는데 이벤트 기능 설정 파라미터event_timeout_in_ms 보다 오래 걸리면 아래와 같은 경고 로그가 출력되며, 해당 이벤트 뿐만 아니라 해당 이벤트와 같은 이벤트 태그를 갖는 모든 이벤트들의 처리가 취소됩니다.

Event timeout: event_name={이벤트이름}, event_id={이벤트아이디}, event_tag={이벤트태그}

이벤트가 강제 취소되면 아래와 같이 핸들러로 통보 받을 수 있습니다.

// 서버의 Install 함수
static bool Install(const ArgumentMap &arguments) {
  ...
  Event::RegisterTimeoutHandler(OnEventTimeout);

  return true;
}

void OnEventTimeout(const string &event_name, const EventId &event_id,
                    const EventTag &event_tag,
                    const Ptr<Session> &associated_session) {
  // 만약 해당 event 가 session message handler 에서 파생된 event 라면
  // associated_session 로 해당 session 이 전달됩니다.
  if (associated_session) {
    // MANIFEST/SessionService 항목의 close_session_when_event_timeout 값이
    // true 이면 이벤트 타임아웃 핸들러 이후에 세션 닫힘 핸들러가 실행되는데,
    // 아래 로그아웃 처리를 세션 닫힘 핸들러에서 처리하는 것이 더 좋습니다.
    Event::Invoke(bind(OnLogout, associated_session));
  }
}

void OnLogout(const Ptr<Session> &session) {
  ...
}

C# 버전은 추후 지원됩니다.

Important

병목 이벤트에 영향을 받지 않기 위해서, 타임아웃 핸들러는 이벤트 쓰레드가 아닌 다른 쓰레드에서 처리됩니다. 그러니 이벤트 쓰레드 안에서 처리해야되는 것이 있다면 Event::Invoke 를 이용해야됩니다.

Tip

만약 해당 이벤트가 세션에 연관된 이벤트일 경우, 이벤트 태그 부여 규칙에 의해 해당 세션으로부터 오는 모든 메시지가 영향을 받습니다. 이는 해당 세션이 더이상 동작하지 않음을 의미하기 때문에 이벤트 타임아웃 핸들러에서 강제 로그아웃 처리를 해야합니다.(단, MANIFEST/SessionService 의 close_session_when_event_timeout 값이 true 일 경우 세션 닫힌 햄들러가 호출되기 때문에 로그아웃 처리를 꼭 여기서 할 필요는 없습니다.)

이벤트 스레드 hang 감지

이벤트 기능 설정 파라미터enable_event_thread_checkertrue 로 설정하면, 이유를 불문하고 이벤트 쓰레드가 30초 이상 멈출 때 아래와 같은 로그가 1분 간격으로 출력됩니다. 이런 경우에는 해당 이벤트 핸들러의 구현에 데드락, 무한루프, 또는 비정상으로 오래 걸리는 처리가 있는지 확인해보실 것을 권장합니다.

event thread hang: event_thread_index=..., event_name={이벤트이름}, event_id={이벤트아이디}, event_tag={이벤트태그}, elapsed_time_in_sec=...

이벤트 프로파일링: 요약 정보

Important

이 기능은 이벤트 기능 설정 파라미터enable_event_profilertrue 이고, ApiService 기능이 켜져있을 때 작동합니다.

아이펀 엔진은 이벤트 시스템 전체에 대한 처리시간 통계를 제공합니다. 이때 타임아웃되어 처리가 중단된 이벤트는 제외됩니다.

통계를 보기 위해서는 다음과 같이 제공되는 API 를 호출합니다.

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/summary/

위 API 를 호출하면 JSON 이 반환되는데, JSON 속성 중에 all_time 은 누적값을 의미하며. last1min 은 최근 1 분 전 통계를 의미합니다. 두 경우 모두 통계 결과에 다음과 같은 항목들이 포함됩니다.

실행 소요 시간 관련:

  • count: 실행된 이벤트의 총 개수

  • execution_time_mean_in_sec: 평균 실행 시간

  • execution_time_stdev_in_sec: 실행 시간 표준편차

  • execution_time_max_in_sec: 최대 실행 시간

I/O 대기 시간 관련:

  • io_wait_time_mean_in_sec: 평균 대기 시간(DB, Zookeeper, Lock 경합 등에 의한)

  • io_wait_time_stdev_in_sec: 대기 시간 표준편차

  • io_wait_time_max_in_sec: 최대 대기 시간

이벤트 큐 관련:

  • queue_time_mean_in_sec: 이벤트 큐에서 머문 평균 시간

  • queue_time_stdev_in_sec: 이벤트 큐에서 머문 시간 표준편차

  • queue_time_max_in_sec: 이벤트 큐에서 머문 최대 시간

Note

execution_time = queue_time + io_wait_time + 이벤트 핸들러 처리 시간

{
    "all_time": {
        "count": 8814,
        "execution_time_mean_in_sec": 0.007857,
        "execution_time_stdev_in_sec": 0.023191,
        "execution_time_max_in_sec": 0.309402,
        "io_wait_time_mean_in_sec": 0.005639,
        "io_wait_time_stdev_in_sec": 0.017964,
        "io_wait_time_max_in_sec": 0.247697,
        "queue_time_mean_in_sec": 0.000953,
        "queue_time_stdev_in_sec": 0.005887,
        "queue_time_max_in_sec": 0.106234
    },
    "last1min": {
        "count": 5882,
        "execution_time_mean_in_sec": 0.009843,
        "execution_time_stdev_in_sec": 0.028,
        "execution_time_max_in_sec": 0.309402,
        "io_wait_time_mean_in_sec": 0.007114,
        "io_wait_time_stdev_in_sec": 0.021708,
        "io_wait_time_max_in_sec": 0.247697,
        "queue_time_mean_in_sec": 0.001377,
        "queue_time_stdev_in_sec": 0.007167,
        "queue_time_max_in_sec": 0.106234
    }
}

이벤트 프로파일링: 상세 정보

Important

이 기능은 이벤트 기능 설정 파라미터enable_event_profilertrue 이고, ApiService 기능이 켜져있을 때 작동합니다.

전체 이벤트의 요약 정보 외에 각 이벤트 별로 처리시간 및 ORM 관련 통계 역시 제공됩니다.

통계를 보기 위해서는 다음 URL 들을 호출합니다.

  • 개별 이벤트 보기

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/each/{이벤트이름}
  • 모든 이벤트 보기

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/all
  • 모든 이벤트 보기 - 이벤트 처리에 걸린 시간의 총합으로 정렬

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/order_by_execution_time_sum
  • 모든 이벤트 보기 - 이벤트 함수에서 머문 시간의 총합으로 정렬

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/order_by_handler_time_sum
  • 모든 이벤트 보기 - I/O 대기 시간의 총합으로 정렬

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/order_by_io_wait_time_sum
  • 모든 이벤트 보기 - 평균 이벤트 처리시간으로 정렬

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/order_by_execution_time
  • 모든 이벤트 보기 - 평균 이벤트 함수에서 머문 시간으로 정렬

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/order_by_handler_time
  • 모든 이벤트 보기 - 평균 I/O 대기 시간으로 정렬

GET http://{ip}:{api-service-port}/v1/counters/funapi/event/profiling/order_by_io_wait_time

결과값으로 JSON 이 반환되며, JSON 에는 이벤트 이름별로 구분되어 통계치를 포함합니다. 각 이벤트 이름별로 all_time 이라는 속성과 last1min 이라는 속성이 포함되는데, 각각 누적 통계와 최근 1분 통계를 의미합니다. 통계의 각 항목의 의미는 다음과 같습니다.

실행 소요 시간 관련:

  • execution_count: 실행 횟수

  • rollback_count_mean: 평균 롤백 횟수

  • rollback_count_max: 최대 롤백 횟수

  • execution_time_mean_in_sec: 이벤트 처리에 걸린 평균 시간

  • execution_time_stdev_in_sec: 이벤트 처리에 걸린 시간의 표준 편차

  • execution_time_max_in_sec: 이벤트 처리에 걸린 최대 시간

  • timeout_count: timeout 처리된 횟수

Note

execution_time = queue_time + io_wait_time + handler_time

이벤트 함수 관련:

  • handler_time_mean_in_sec: 이벤트 함수에서 머문 평균 시간

  • handler_time_stdev_in_sec: 이벤트 함수에서 머문 시간 표준편차

  • handler_time_max_in_sec: 이벤트 함수에서 머문 최대 시간

I/O 대기 시간 관련:

  • io_wait_time_mean_in_sec: 평균 대기 시간(DB, Redis, Lock 경합 등에 의한)

  • io_wait_time_stdev_in_sec: 대기 시간 표준편차

  • io_wait_time_max_in_sec: 최대 대기 시간

이벤트 큐 관련:

  • queue_time_mean_in_sec: 이벤트 큐에서 머문 평균 시간

  • queue_time_stdev_in_sec: 이벤트 큐에서 머문 시간 표준편차

  • queue_time_max_in_sec: 이벤트 큐에서 머문 최대 시간

ORM 관련:

  • object_create_count_mean: 생성한 object 평균 개수

  • object_count_mean: fetch 한 object 평균 개수(Fetch 를 했으나 존재하지 않는 경우는 제외)

  • object_cache_hit_rate_mean: fetch 한 object 가 IO 없이 in-memory cache 에서 얻어진 비율

    이 값이 높을 수록 좋습니다. 이 값은 Fetch 를 했으나 존재하지 않는 경우 역시 포함하며, 따라서 음수가 될 수 있습니다. 이 값이 낮다면 DB 캐싱 의 설명을 참고하여 object 가 더 오래 cache 에 머물도록 해야합니다.

  • object_nolock_rate_mean: kReadCopyNoLock 으로 fetch 한 object 의 비율 (Fetch 했으나 존재하지 않는 경우는 제외)

  • object_lease_rate_mean: DB 나 cache 가 아닌 RPC 로 다른 서버에서 빌려온 object 의 비율.

    이 값은 낮을 수록 좋습니다. (Fetch 했으나 존재하지 않는 경우는 제외)

예)

{
    "OnGameServerLogin": {
        "all_time": {
            "execution_count": 11633,
            "rollback_count_mean": 0.0,
            "rollback_count_max": 2,
            "execution_time_mean_in_sec": 0.000195,
            "execution_time_stdev_in_sec": 0.000867,
            "execution_time_max_in_sec": 0.090001,
            "handler_time_mean_in_sec": 0.000123,
            "handler_time_stdev_in_sec": 0.000057,
            "handler_time_max_in_sec": 0.002703,
            "io_wait_time_mean_in_sec": 0.000023,
            "io_wait_time_stdev_in_sec": 0.000854,
            "io_wait_time_max_in_sec": 0.089414,
            "queue_time_mean_in_sec": 0.000049,
            "queue_time_stdev_in_sec": 0.000039,
            "queue_time_max_in_sec": 0.00113,
            "object_count_mean": 2.0,
            "object_cache_hit_rate_mean": 0.995,
            "object_nolock_rate_mean": 0.029,
            "object_lease_rate_mean": 0.0,
            "object_null_rate_mean": 0.0,
            "object_create_count_mean": 0.0,
            "timeout_count": 0
        },
        "last1min": {
            "execution_count": 60,
            "rollback_count_mean": 0.0,
            "rollback_count_max": 1,
            "execution_time_mean_in_sec": 0.000205,
            "execution_time_stdev_in_sec": 0.000212,
            "execution_time_max_in_sec": 0.001727,
            "handler_time_mean_in_sec": 0.00013,
            "handler_time_stdev_in_sec": 0.00005,
            "handler_time_max_in_sec": 0.000414,
            "io_wait_time_mean_in_sec": 0.000023,
            "io_wait_time_stdev_in_sec": 0.000177,
            "io_wait_time_max_in_sec": 0.00137,
            "queue_time_mean_in_sec": 0.000052,
            "queue_time_stdev_in_sec": 0.000048,
            "queue_time_max_in_sec": 0.00032,
            "object_count_mean": 2.0,
            "object_cache_hit_rate_mean": 0.992,
            "object_nolock_rate_mean": 0.025,
            "object_lease_rate_mean": 0.0,
            "object_null_rate_mean": 0.0,
            "object_create_count_mean": 0.0
        }
    },
    "OnListFriend": {
        "all_time": {
            "execution_count": 1272,
            "rollback_count_mean": 0.4,
            "rollback_count_max": 2,
            "execution_time_mean_in_sec": 0.001434,
            "execution_time_stdev_in_sec": 0.002105,
            "execution_time_max_in_sec": 0.009127,
            "handler_time_mean_in_sec": 0.000522,
            "handler_time_stdev_in_sec": 0.000304,
            "handler_time_max_in_sec": 0.002494,
            "io_wait_time_mean_in_sec": 0.000849,
            "io_wait_time_stdev_in_sec": 0.001794,
            "io_wait_time_max_in_sec": 0.007847,
            "queue_time_mean_in_sec": 0.000064,
            "queue_time_stdev_in_sec": 0.000056,
            "queue_time_max_in_sec": 0.000581,
            "object_count_mean": 14.1,
            "object_cache_hit_rate_mean": 0.854,
            "object_nolock_rate_mean": 0.929,
            "object_lease_rate_mean": 0.0,
            "object_null_rate_mean": 0.071,
            "object_create_count_mean": 0.0,
            "timeout_count": 0
        },
        "last1min": {
            "execution_count": 11,
            "rollback_count_mean": 0.4,
            "rollback_count_max": 2,
            "execution_time_mean_in_sec": 0.001115,
            "execution_time_stdev_in_sec": 0.001668,
            "execution_time_max_in_sec": 0.00521,
            "handler_time_mean_in_sec": 0.000467,
            "handler_time_stdev_in_sec": 0.000292,
            "handler_time_max_in_sec": 0.001113,
            "io_wait_time_mean_in_sec": 0.000595,
            "io_wait_time_stdev_in_sec": 0.001362,
            "io_wait_time_max_in_sec": 0.003993,
            "queue_time_mean_in_sec": 0.000054,
            "queue_time_stdev_in_sec": 0.000031,
            "queue_time_max_in_sec": 0.000104,
            "object_count_mean": 13.6,
            "object_cache_hit_rate_mean": 0.867,
            "object_nolock_rate_mean": 0.927,
            "object_lease_rate_mean": 0.0,
            "object_null_rate_mean": 0.073,
            "object_create_count_mean": 0.0
        }
    },
    ...
}

이벤트 기능 설정 파라미터

아래의 설명과 콤포넌트 구조와 설정 방법설정 파일 (MANIFEST.json) 상세 를 참고하여 EventDispatcher 를 설정할 수 있습니다.

  • event_threads_size: 메인 이벤트 쓰레드 개수. (type=uint64, default=4)

  • enable_event_profiler: 이벤트 프로파일러의 활성화 여부 (type=bool, default=true)

직접 설정을 바꿀 일이 거의 없는 파라미터들

  • slow_event_log_threshold_in_ms: 어느 정도로 느린 이벤트들을 로그로 남길지 지정 (type=uint64, default=300)

  • event_timeout_in_ms: 이벤트를 타임 아웃시킬 때까지 걸리는 밀리초 시간 (type=uint64, default=30000)

  • enable_inheriting_event_tag: Event::Invoke() 를 호출할 때 별도의 event tag 를 지정하지 않을 경우, 호출한 event 의 event tag 를 상속 받을지 여부 (type=bool, default=true)

  • enable_random_event_tag: enable_inherit_event_tag 마저 활성화되지 않은 경우 Event::Invoke() 를 아무 event tag 없이 호출할 때 랜덤하게 event tag 를 생성해서 부여할지 여부. 만일 false 이면 null event tag 를 부여함 (type=bool, default=true)

  • enable_event_thread_checker: 만일 true 이면 event thread 가 처리 중 blocking 됐는지 여부를 1초에 한번씩 체크함 (type=bool, default=true)

  • enable_outstanding_event_profiler: 현재 실행되는 이벤트들을 프로파일링할지 여부 (type=bool, default=true)