[6] NS-3 Tracing(1)

최윤하·2023년 2월 6일
0

NS-3 Simulator

목록 보기
6/7
post-thumbnail

이번에는 tracing에 대해서 자세히 알아보겠습니다.

Tracing

시뮬레이션의 가장 큰 목적은 연구에 사용할 유의미한 output을 만들어내는 것입니다.
ns-3에서 output을 얻는 전략은 크게 2가지가 있습니다.

먼저, 미리 정의된 bulk(많은 양) output 메커니즘을 사용하고, 그 output에서 유의미한 정보를 뽑아내는 방법.
아니면 원하는 정보만 정확히 주는 output 메커니즘을 직접 개발하는 방법이 있습니다.

전자의 경우, 유의미한 데이터를 뽑아내기 위해(parsing) 스크립트를 작성하는 것 말고는 ns-3에 대해 아무것도 변경하지 않아도 된다는 장점이 있습니다. 예를 들어, PCAP와 NS_LOG output 메시지가 여기에 속합니다.
하지만 parsing을 위해 스크립트를 작성하는 것 또한 비용이 발생합니다.
게다가 NS_LOG output은 오직 debug build에서만 이용 가능해서 성능에 패널티를 받게 됩니다.(optimized mode는 2배 빠름)

그래서 전자의 다양한 문제점을 피하기 위해 Tracing 메커니즘이 제공됩니다.
정확히 말하면 "low-level" tracing이라고 할 수 있습니다.

high-level tracing은 trace helper를 사용해서 미리 정의된 source 정보를 얻는 것입니다. sink를 생성하거나 source와 sink를 직접 연결하는 과정이 없습니다.
low-level은 source를 직접 custom sink에 연결합니다.

tracing은 몇가지 장점을 가지고 있습니다.
1) 관리해야 할 데이터를 줄일 수 있습니다. (오직 관심있는 이벤트만 tracing 해서)
2) output의 형식을 컨트롤 해서 후처리 과정을 피할 수 있습니다.

tracing의 개념을 다시 한 번 정리해보겠습니다.

tracing system은 각각 독립적인 tracing source와 tracing sink의 개념으로 설계되었습니다.
그리고 source를 sink에 연결하는 메커니즘을 사용합니다.

Trace source

  • 시뮬레이션에서 발생하는 이벤트를 알려주는 아이
  • 흥미로운 데이터에 대한 접근을 제공
    예를 들어, TCP의 congestion window
    window의 크기가 변할 때 마다, 연결된 sink는 old와 new value를 전달받습니다.
  • source 자체로는 무쓸모
    source에 의해 제공된 정보로 유의미한 일을 할 수 있는 코드에 반드시 연결되어야 합니다.

Trace sink

  • 정보의 소비자
  • = callback function
    sink가 trace event를 받고싶으면, trace source가 가지고 있는 Callback 리스트에 자신을 추가합니다.

Callback 이란?
인자로 넘겨지는 함수
함수를 등록하기만 하고, 어떤 이벤트가 발생했거나 특정 시점에 도달했을 때 시스템에서 호출하는 함수

시나리오 분석(fourth.cc)

이제 tracing을 사용한 간단한 예제를 보겠습니다.

일단, tracing system은 Attributes, Attributes와 함께 동작하는 Object와 통합되어있습니다.
그래서 trace source를 위한 ns-3 Object가 반드시 필요합니다.

class MyObject : public Object
{
public:
  static TypeId GetTypeId (void)
  {
	static TypeId tid = TypeId ("MyObject")
	  .SetParent (Object::GetTypeId ())
	  .SetGroupName ("MyGroup")
	  .AddConstructor<MyObject> ()
	  .AddTraceSource ("MyInteger",
					   "An integer value to trace.",
                       MakeTraceSourceAccessor (&MyObject::m_myInt), 
                       "ns3::TracedValueCallback::Int32")
	  ;
	 return tid;
   }

	MyObject () {}
    TracedValue<int32_t> m_myInt;
};

위 코드에서 중요한 부분이 2개 있습니다.

.AddTraceSource는 trace source를 바깥 세상으로 연결하는데 사용되는 "hooks(갈고리)"를 제공합니다.
첫번째 인자는 source의 이름, 두번째는 도움 문자열, 세번째 인자의 m_myInt는 TracedValue입니다.

MyObject 클래스에서 data 멤버인 m_myInt는 TracedValue<>로 선언되었는데, 이 값이 변경되면 TracedValue 메커니즘이 old와 new value를 모두 제공합니다. 그래서 이것에 대한 trace sink 함수는 다음과 같은 형태를 갖춰야 합니다.

void (* traceSink)(int32_t oldValue, int32_t newValue);

그래서 이것을 적용하면 아래의 함수를 만들 수 있습니다.

void
IntTrace (int32_t oldValue, int32_t newValue)
{
	std::cout << "Traced " << oldValue << " to " << newValue << std::endl;
}

일단 연결되면 TracedValue가 바뀔 때 마다 이 함수가 호출될 것 입니다.

이제 source를 sink에 연결해보겠습니다.

int
main (int argc, char *argv[])
{
	Ptr<MyObject> myObject = CreateObject<MyObject> ();
	myObject->TraceConnectWithoutContext ("MyInteger", MakeCallback(&IntTrace));

	myObject->m_myInt = 1234;
}

먼저, MyObject 인스턴스를 만들어 줍니다.
그리고 TraceConnectWithoutContext로 source와 sink를 연결해줍니다.
첫번째 인자는 trace source 이름이고, 두번째는 callback 함수를 전달해줍니다.

그리고 main에서 아래와 같이 값을 바꿔주면

myObject->m_myInt = 1234;

m_myInt가 TracedValue이기 때문에, '=' 연산자가 callback을 실행시킵니다.

그리고 시나리오를 실행하게되면

trace source가 바뀌자 마자 source는 sink에게 old와 new value를 제공하였고, 그걸 받은 IntTrace 함수로부터 메시지가 출력되는 것을 확인할 수 있습니다.

Connect with Config

사실 위 예제에서 사용한 TraceConnectWithoutContext은 실제 시스템에서 잘 사용되지 않습니다.
대신 Config path를 사용해서 trace source를 선택하는 Config system이 주로 사용됩니다.

third.cc에서 "CourseChange" trace source를 Config path를 사용해서 직접 만든 trace sink에 연결했었습니다.

아래의 코드를 사용했습니다.

std::ostringstream oss;
oss << "/NodeList/"
	<< wifiStaNodes.Get (nWifi - 1)->GetId ()
	<< "/$ns3::MobilityModel/CourseChange";

Config::Connect (oss.str (), MakeCallback (&CourseChange));

Config::ConnectWithoutContext와 Config::Connect은 실제로 내부에서는 Ptr<Object>를 찾고, 적절한 TraceConnect 메서드를 호출합니다.(fourth.cc에서 일일이 해주었던 것 처럼!!)

그렇다면 이제 다음과 같은 질문들이 생길겁니다.
1) 무슨 trace source가 이용 가능한지 어떻게 찾아?
2) source는 찾았는데 Config path는?
3) source랑 Config path는 찾았는데, 내 callback 함수의 리턴 타입이랑 argument는 어떻게 알아내?

순서대로 알아보겠습니다.

Q1. 이용 가능한 trace source를 어떻게 찾아?
ns-3 API 문서를 보면 됩니다.
All TraceSources List

Q2. Config path는 어떻게 알아내?
관심있는 object의 "Detailed Description" 섹션을 보면 됩니다.

또는, grep 명령어로 이미 있는 코드에서 찾을 수 있습니다.

$ find . -name '*.cc' | xargs grep CourseChange | grep Connect

Q3. Callback 함수의 리턴 타입과 인자는?

이제, 다음 예제를 위한 연습을 해보겠습니다.

먼저, API 문서 All TraceSources List에서 찾고싶은 source를 검색해줍니다.

그리고 클릭해서 Detailed description에서 Config path와 callback signature(즉, 리턴 타입과 필요한 인자 형태를 의미)를 찾아줍니다.

이제 ns3::TcpSocketBase 객체에 대한 포인터만 있으면, 우리는 적절한 callback target 함수를 제공한다면 CongestionWindow에 TraceConnect를 할 수 있습니다.

이거를 처음부터 새로 만들기보다는 이미 있는 코드에서 수정하는 것이 좋습니다.

$ find . -name '*.cc' | xargs grep CongestionWindow

❗❗초보자의 수준에서 작성 된 글입니다❗❗
잘못 된 내용이 있으면 피드백 부탁드립니다.

0개의 댓글