본문

OMNeT++ Signals #2. Signal-Based Statistics Recording-1

시그널은 결과값이 처리될 목적지를 지정하지 않고도, 변수를 외부에 노출시키는 역할을 한다. 모듈은 결과값을 갖는 변수를 발행(publish) 하고, 결국 결과값은 리스너에서 처리되게 된다. 리스너는 시뮬레이션 프레임워크 또는 다른 모듈상에 추가되어 사용될 수 있다. 시그널 구조를 사용하여, 순차적으로 모든 값을 저장하거나 혹은 평균값, 최대/최소값, 편차, 분포와 같이 다양한 형태를 사용하여 값을 저장할 수 있으며, 또한 값 저장 이전에 선처리 과정 또한 추가할 수 있다.


시그널에 근거하여 시뮬레이션 결과값을 기록하고자 한다면, simple모듈(혹은 채널)의 NED 정의에서 @statistic 속성(property)을 지정해 주어야 한다. @statistic 속성은, 통계의 이름을 정의하고 어떤 시그널이 입력으로 사용될 것이며, 어떤 작업이 결과값을 처리하는데 사용될 것인가(값 보정, 필터링, 합계계산, 미분계수 적용), 어떤 속성이 기록될 것인가(최소값, 최대값, 평균 등), 어떤 형태로 저장될 것인가(벡터, 스칼라, 히스토그램)에 대해 정의하는 역할을 한다. 각 항목들은 선택적(optional)으로 기록될 수 있으며, 이때 default 혹은 all 집합을 사용하여 저장될 항목들을 지정할 수 있다. (기록될 항목들에 대해서는 설정에서 추가적으로 지정될 수 있다). 통계기록를 위해 특정한 이름(title)을 또는 수치 단위를 명시할 수 있다.


simple Queue{

     parameters:

          @statistic[queueLength](record=max,timeavg,vector?);

     gates:

          input in; output out;

}


위에서 보다시피, 통계 설정은 인덱스로 구분되는 NED 속성으로 표현된다. 속성명은 항상 statistic이며, 인덱스 항목(위에서는 queueLength)은 통계에 사용되는 이름이다. 속성값은 괄호안에서 설정되며, 여기에는 기록할 정보에 대한 지시사항이라든가 다른 기타 사항이 저장되게 된다. 위에서의 @statistic 선언에서 가정할 것은, 모듈에서의 C++ 코드에 의해 변화되는 큐의 사이즈가, 개체(element)가 큐에 삽입되거나 제거될 때마다, queueLength 시그널을 통해 전달된다는 것이다. 기본적으로, 큐 사이즈의 최대값과 평균값(record=max,timeavg)이 스칼라의 형태로 기록 될 것이다. 모든 결과 값을 기록하도록('all') 설정할 경우, 이때 큐 사이즈 뿐 아니라 '?'(optional)으로 설정된 항목(vector)또한 기록될 것이다. (벡터에 대해서는 다음 포스팅에서 설명하겠습니다. 간단히 정리하자면 스칼라는 통계 결과 -평균이나 총 합계-를 나타내며, 벡터는 통계과정상에서 발생하는 raw값들을 의미합니다) 위 예제에서, 기록될 시그널의 이름은 통계에서 사용되는 이름과 동일하다(queueLength). source 속성 키를 사용하여 다른 시그널을 입력으로 받을 수도 있다. 아래의 예제는 c++ 코드에서 qlen 시그널을 호출하고, 이를 기반으로 queueLength 통계를 선언한다.


simple Queue {

     parameters:

          @signal[qlen](type=int); // optional

          @statistic[queueLength](source=qlen; record=max,timeavg,vector?);

}


참고할 것은, source=qlen 속성 키 이후에 qlen 시그널을 위해 시그널 선언부(@signal 속성)를 추가했다는 것이다. 시그널을 선언하는 것은 현재로서는 선택적인 사항이며, 사실 @signal 속성은 시스템 상에서는 무시된다.(하지만 명시하는 습관을 들이는 측면에서는 좋다). 시그널을 받아 자료를 저장하기 전 선처리 작업을 등록하는 것이 가능하다. 아래의 예를 보자.


@statistic[dropCount](source="count(drop)"; record=last,vector?);


여기에서는 c++ 코드에서 패킷이 유실될 때마다 drop 시그널을 호출하고, 이로서 유실된 패킷의 수(last)를 스칼라의 형태로 저장하고, 선택적('?')으로, 시간 함수에서 유실된 패킷의 수는 벡터로 저장된다. drop시그널의 데이터 값 그리고 데이터 타입은 여기에서는 중요하지 않은데, 왜냐하면 최종적으로는 시그널이 발생된 수만이 세어질 것이기 때문이다. 여기에서 count()는 결과 필터로서 작동한다. 참고로, "count(drop)" 양쪽에 둘러있는 쌍따옴표를 사용한 이유는, NED 언어 파서기에서는 속성값을 처리할때엔 괄호 표현을 사용하지 않기 떄문이다.


@statistic[droppedBytes](source="sum(packetBytes(pkdrop))"; record=last, vector?);


이 예제는 c++코드가 pkdrop 시그널을 호출하고 이때 패킷(cPacket* 포인터)를 값으로 받아올 때를 가정한다. 이 시그널에 기반하여, 위 코드를 통해 유실된 총 바이트를 스칼라의 형태로(혹은 벡터형식은 옵션으로)기록한다. packetBytes()필터는 cPacket의 getByteLength() 메소드를 통하여 각 패킷에 대한 바이트 길이를 추출하고, sum()필터는 이 모두를 더한다.


수학적 표현 또한 사용될 수 있다. 예를 들어, 아래의 예제는 packetBits() 필터를 사용하여 유실된 바이트를 계산한다.

@statistic[droppedBytes](source="sum(8*packetBits(pkdrop))"; record=last,vector?);

@statistic[dropRate](source="count(drop)/count(pk)"; record=last,vector?);


여러개의 시그널이 사용된 경우, 각 시그널로 도착한 값은 하나의 출력을 이끌어 낸다. 계산은 시그널들 중으로 부터의 가장 마지막 값을 사용하여 이루어 질 것이다. 여러개의 시그널을 사용할 때는, (오류를 방지하기 위해) 동일한 시그널은 두번 이상 일어나지 않는다는 제한이 있다.


기록(record) 항목의 경우 이 역시 수식의 형태로 표현되거나 혹은 필터를 포함할 수 있다. 예를 들어, 아래의 통계를 보자면 위에 있는 예제들과 기능적으로 동일하다. 이것 역시 cPacket* 을 전달하는 시그널을 입력으로 하여, 유실된 총 바이트를 계산하고 이를 스칼라와 벡터 값으로 저장한다. (단, 일부 계산의 경우 record 부분에서 처리된다)

@statistic[droppedBytes](source="packetBits(pkdrop)"; record="last(8*sum)","vector(8*sum)?");


* 속성 키

아래는 @statistic 속성에서 인식되는 키들을 모아놓은 것이다.

source : 기록을 위한 입력을 정의한다. 누락된 경우, 통계 이름이 시그널 이름으로 등록된다.

record : ,로 분리된 기록 모드의 리스트를 담고 있다. 어떻게 source를 기록할 것이에 대해 정의한다.

title : 통계 시그널을 위한, 길고 서술적인 이름이다. 그래픽 출력 툴에서 이를 차트의 라벨(예를 들어 범례)로 사용할 수 있다.

unit : 값의 단위를 나타낸다. 역시 차트에 보여진다.

interpolationmode : 그래픽 출력등을 위해, 어떻게 시그널 값을 보간(interpolate)할것인가에 대해 정의한다. 가능한 값으로서, none, sample-hold, backward-sample-hold, linear가 있다.

enum : 정수형 시그널 값을 위한 심볼릭 이름을 정의한다. 이 속성은 반드시 문자열 값을 가져야 하며, "이름=값"쌍이 ,로 연결된 형태를 취해야 한다. 예) "IDLE=1,BUSY=2,DOWN=3"


결과 항목이 기록되면 이름은 정적 이름과 기록 모드의 조합으로 다음과 같이 만들어 진다. "<statisticName>:<recordingMode>" 따라서 아래와 같이 정의된 통계는 dropRate:last, droppedBytes:sum 으로 명명된 스칼라 값과 dropRate:vector, droppedBytes:vector(sum)으로 명명된 벡터값을 만들어 낼것이다. 


@statistic[dropRate](source="count(drop)/count(pk)"; record=last,vector?);

@statistic[droppedBytes](source="packetBytes(pkdrop)"; record=sum,"vector(sum)?");


record를 제외한 모든 속성 키에 의한 결과값은 벡터 혹은 스칼라 파일로서 저장될 것이다. title 속성은 기록 전에 수정되어, 이 뒤에 ,가 붙은채로 기록 모드명이 기입될 것이다. 그 이외의 경우에는, 동일한 통계로부터 저장된 모든 결과 항목은 동일한 이름으로 저장될 것이다. 예) "Dropped Bytes, sum", "Dropped Bytes, vector(sum)"

물론 다른 속성 키를 사용하는것이 허용되긴 하지만 OMNeT++ 프로그램 혹은 결과 분석 툴에서는 인식되지 않을 것이다.


* source와 record에 대해

source와 record를 완전히 이해하기 위해선, 어떻게 결과 값이 구성되는지 알 필요가 있다.

모듈이나 채널이 시뮬레이션 상에서 만들어지면, OMNeT++ 프로그램은 NED 선언상에서 @statistic 속성을 검사하고, 시그널에 해당하는 리스너를 등록한다. 결과값을 기록하는데에는 result filter와 result recorder 이 두가지의 리스너가 연관되어있다. result filter는 서로 연결될 수 있으며(chained), 그 종단에는 항상 recorder가 존재해야 한다. 따라서, recorder는 시그널에 바로 붙어 작업될 수 있으며, 혹은 다수의 filter와 하나의 recorder가 결합되어 사용될 수 있다. 이것을 파이프 라인 혹은 파이프 트리라고 생각해보자, 트리의 루트에는 시그널이 위치해 있고, 말단에는 result recorder가 있으며, 그 중간에는 result filters가 있다고 생각하면 된다.


result filter는 입력으로 받은 값(물론 이전 filter에서 받은 값이 될 수도 있고, 시그널 자체에서의 값이 될 수도 있다)들을 처리하고, 이를 출력으로 내보낸다(대상은 이 뒤에 이어진 filter 혹은 recorder가 될 수 있다) filter는 값을 자신만 갖고(not propagate) 출력을 내보내지 않을 수도 있다. recorder는 수신한 값을 출력 벡터로서 저장하거나, 시뮬레이션이 끝날때 출력 스칼라로 저장할 수 있다.


많은 동작(operation)이 filter와 recorder에 존재한다. 예를 들어, sum filter의 경우, 입력받은 값들의 합을 출력으로 내보낸다(propagate) 그리고 sum recorder는 입력받은 값들의 합을 시뮬레이션 종료시 출력 스칼라로 저장한다. 아래의 그림은 다음 통계 정의에 대해, 어떤 filter/recorder가 생성되고, 어떻게 서로가 연결되어있는지 나타낸다.

@statistic[droppedBytes](source="8*packetBits(pkdrop)"; record="sum,vector(sum)");




원문출처 : OMNeT++ User Manual Version 4.2.2 : 4.15 Signal-Based Statistics Recording(~4.15.2)

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.