Microsoft MVP성태의 닷넷 이야기
Linux: 117. eBPF / bpf2go - Map에 추가된 요소의 개수를 확인하는 방법 [링크 복사], [링크+제목 복사],
조회: 4001
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

(시리즈 글이 14개 있습니다.)
Linux: 86. Golang + bpf2go를 사용한 eBPF 기본 예제
; https://www.sysnet.pe.kr/2/0/13769

Linux: 94. eBPF - vmlinux.h 헤더 포함하는 방법 (bpf2go에서 사용)
; https://www.sysnet.pe.kr/2/0/13783

Linux: 95. eBPF - kprobe를 이용한 트레이스
; https://www.sysnet.pe.kr/2/0/13784

Linux: 96. eBPF (bpf2go) - fentry, fexit를 이용한 트레이스
; https://www.sysnet.pe.kr/2/0/13788

Linux: 100.  eBPF의 2가지 방식 - libbcc와 libbpf(CO-RE)
; https://www.sysnet.pe.kr/2/0/13801

Linux: 103. eBPF (bpf2go) - Tracepoint를 이용한 트레이스 (BPF_PROG_TYPE_TRACEPOINT)
; https://www.sysnet.pe.kr/2/0/13810

Linux: 105. eBPF - bpf2go에서 전역 변수 설정 방법
; https://www.sysnet.pe.kr/2/0/13815

Linux: 106. eBPF / bpf2go - (BPF_MAP_TYPE_HASH) Map을 이용한 전역 변수 구현
; https://www.sysnet.pe.kr/2/0/13817

Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
; https://www.sysnet.pe.kr/2/0/13819

Linux: 109. eBPF / bpf2go - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
; https://www.sysnet.pe.kr/2/0/13824

Linux: 110. eBPF / bpf2go - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
; https://www.sysnet.pe.kr/2/0/13825

Linux: 115. eBPF (bpf2go) - ARRAY / HASH map 기본 사용법
; https://www.sysnet.pe.kr/2/0/13893

Linux: 116. eBPF / bpf2go - BTF Style Maps 정의 구문과 데이터 정렬 문제
; https://www.sysnet.pe.kr/2/0/13894

Linux: 117. eBPF / bpf2go - Map에 추가된 요소의 개수를 확인하는 방법
; https://www.sysnet.pe.kr/2/0/13895




eBPF / bpf2go - Map에 추가된 요소의 개수를 확인하는 방법

BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_RINGBUF 등의 stream map이 아닌 BPF_MAP_TYPE_HASH같은 경우에는 추가된 요소의 개수를 확인하고 싶을 때가 있습니다. 그런데 아무리 문서를 찾아봐도,

Map helpers
; https://docs.ebpf.io/linux/helper-function/#map-helpers

이에 대한 함수가 딱히 없습니다. 혹시나 검색해 보면, 그나마 max_entries 정도의 값만 구할 수 있다는 의견이 나오는데요,

How ti get the size of eBPF map?
; https://stackoverflow.com/questions/72772724/how-ti-get-the-size-of-ebpf-map

BPF map find the number of elements
; https://stackoverflow.com/questions/68459312/bpf-map-find-the-number-of-elements

그렇다면, 직접 해당 맵에 값을 추가 또는 삭제할 때마다 관련 카운트를 함께 증가/감소시켜야만 합니다.

bpf_map_update_elem(&my_map, &key, &value, BPF_ANY);
__sync_fetch_and_add(&count, 1);

bpf_map_delete_elem(&my_map, &key);
__sync_fetch_and_add(&count, -1);

그리곤 저 count 값을 읽어내는 함수를 추가하든가, 아니면 저 정보를 담는 맵을 하나 만들어 연동하든가 해야 합니다.

하지만 위의 코드에는 유의해야 할 점이 하나 있는데요, bpf_map_update_elem에 BPF_ANY 옵션을 주는 경우 값을 신규로 insert한 것인지, 기존에 있던 것을 update한 것인지 반환 값만으로는 알 수 없는 문제가 있습니다. 따라서, 카운팅을 원한다면 상황에 따라 BPF_NOEXIST, BPF_EXIST로 나눠서 호출해야 합니다.

if (bpf_map_update_elem(&my_map, &key, &value, BPF_NOEXIST) == 0) {
    __sync_fetch_and_add(&count, 1); // 신규 insert가 된 경우 (하지만, 만약 이 짧은 순간에 다른 스레드가 bpf_map_update_elem을 했다면?)
} else {
    // BPF_ANY로 동작할 것을 의도했으므로 update 동작을 수행 (하지만, 만약 이 짧은 순간에 다른 스레드가 bpf_map_delete_elem를 했다면?)
    bpf_map_update_elem(&my_map, &key, &value, BPF_EXIST)
}

음... 마음에 안 드는군요. ^^;




제한적으로 사용한다면 약간 우회 방법이 하나 있긴 합니다. 바로 bpf_for_each_map_elem() 함수를 이용하는 것인데요,

ebpf-docs/docs/linux/helper-function/bpf_for_each_map_elem.md
; https://github.com/isovalent/ebpf-docs/blob/master/docs/linux/helper-function/bpf_for_each_map_elem.md

예를 들어, 특정 맵의 항목 수를 알고 싶을 때 다음과 같은 식으로 정의할 수 있습니다.

static long counting_map_callback_fn(struct bpf_map* map, const void* key, void* value, void* ctx) {
    __u64* pcount = (__u64*)ctx;
    __sync_fetch_and_add(pcount, 1);

    return 0; // return 0 to continue iteration, or 1 to stop
}

SEC("socket") int refresh_count() {
    __u64 count = 0;

    long (*callback_func)(struct bpf_map *, const void *, void *, void *) = &counting_map_callback_fn;

    bpf_for_each_map_elem(&my_map, callback_func, &count, 0);

    return count;
}

보는 바와 같이 호출 이후에는 count에 요소의 수가 담겨 있을 것입니다.




eBPF와는 달리 동일한 맵을 Go 측에서 카운팅할 때는 iterator를 이용해 좀 더 직관적으로 코딩할 수 있습니다.

iter := bpfObj.MyMap.Iterate()

var (
    key   [... map의 key 타입 ...]
    value [... map의 value 타입 ...]
)

count := int64(0)
for iter.Next(&key, &value) {
    count++
}

그런데 Iterate 함수와 iter.Next 함수 호출에 대해 혹시나 싶어 다음과 같이 공통 함수를 만들어 둬도 좋지 않을까... 했는데,

func getMapElementCount(m *ebpf.Map) int64 {
    iter := m.Iterate()

    var (
        key   interface{}
        value interface{}
    )

    count := int64(0)
    for iter.Next(&key, &value) {
        count++
    }

    err := iter.Err()
    if err != nil {
        log.Printf("%v\n", err) // 에러 발생: look up next key: binary.Read: invalid type *interface {}
        return -1
    }

    return count
}

아쉽게도 iter.Next가 정확한 key/value 타입의 크기를 알아야 내부에서 역직렬화를 할 수 있기 때문에 저런 경우 "look up next key: binary.Read: invalid type *interface {}" 오류가 발생합니다.

이 외에, eBPF 측에서 bpf_for_each_map_elem을 호출하는 함수를 Go 언어 측에 제공해 호출하는 방법도 있습니다.

inData := make([]byte, 14)
ret, _, err := bpfObj.RefreshCount.Test(inData)

즉, 원할 때마다 RefreshCount.Test를 호출하면 특정 맵의 요소 개수를 알 수 있으므로 디버깅에 도움이 될 것입니다.





참고로 stream map이긴 하지만 비교적 최근에 나온 BPF_MAP_TYPE_RINGBUF는 맵의 정보를 조회하는 함수를 제공하고 있는데요,

bpf_ringbuf_query
; https://docs.ebpf.io/linux/helper-function/bpf_ringbuf_query/

  • BPF_RB_AVAIL_DATA: Amount of data not yet consumed.
  • BPF_RB_RING_SIZE: The size of ring buffer.
  • BPF_RB_CONS_POS: Consumer position (can wrap around).
  • BPF_RB_PROD_POS: Producer(s) position (can wrap around).

long avail_data = 0;
long ring_size = 0;
long cons_pos = 0;
long prod_pos = 0;

avail_data = bpf_ringbuf_query(&my_ringbuf, BPF_RB_AVAIL_DATA);
ring_size = bpf_ringbuf_query(&my_ringbuf, BPF_RB_RING_SIZE);
cons_pos = bpf_ringbuf_query(&my_ringbuf, BPF_RB_CONS_POS);
prod_pos = bpf_ringbuf_query(&my_ringbuf, BPF_RB_PROD_POS);

아마도 향후에는 다른 맵에 대해서도 이런 함수가 추가되지 않을까 싶기도 합니다. ^^




문서에도 나오지만, bpf_for_each_map_elem에 전달하는 4번째 context 변수는 반드시 stack에 있는 주소를 전달해야 합니다. 만약 전역 변수나, 다른 맵의 요소를 사용하면,

__u64 g_count;

SEC("socket") int refresh_count() {
    // ...[생략]...
    
    bpf_for_each_map_elem(&fd_while_enter_exit_map, callback_func, &g_count, 0);

    return g_count;
}

eBPF 코드 로딩 과정에서 이런 오류가 발생합니다.

load program: permission denied: 23: (85) call bpf_for_each_map_elem#164: R3 type=map_value expected=fp (25 line(s) omitted)

그리고 만약 이런 오류가 발생한다면?

program refresh_count: load program: invalid argument: At callback return the register R0 has unknown scalar value should have been in [0, 1] (22 line(s) omitted)

bpf_for_each_map_elem의 callback으로 전달한 함수가 0과 1 이외의 값을 반환하도록 만들어진 경우입니다. 즉, 아래와 같은 식으로 반환하면 오류가 발생합니다.

static long counting_map_callback_fn(struct bpf_map* map, const void* key, void* value, void* ctx) {
    __u64* pcount = (__u64*)ctx;
    __sync_fetch_and_add(pcount, 1);

    return *pcount; // return 0 to continue iteration, or 1 to stop
}

암튼, 사용하기 전에 꼼꼼히 문서를 한번 읽어보실 것을 권장합니다. ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 3/4/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13474정성태12/6/202311647개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
13473정성태12/5/202312251닷넷: 2179. C# - 값 형식(Blittable)을 메모리 복사를 이용해 바이트 배열로 직렬화/역직렬화파일 다운로드1
13472정성태12/4/202311198C/C++: 164. Visual C++ - InterlockedCompareExchange128 사용 방법
13471정성태12/4/202311530Copilot - To enable GitHub Copilot, authorize this extension using GitHub's device flow
13470정성태12/2/202312474닷넷: 2178. C# - .NET 8부터 COM Interop에 대한 자동 소스 코드 생성 도입 [1]파일 다운로드1
13469정성태12/1/202312600닷넷: 2177. C# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법파일 다운로드1
13468정성태12/1/202311485닷넷: 2176. C# - .NET Core/5+부터 달라진 RCW(Runtime Callable Wrapper) 대응 방식파일 다운로드1
13467정성태11/30/202312393오류 유형: 882. C# - Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5)파일 다운로드1
13466정성태11/29/202311619닷넷: 2175. C# - DllImport 메서드의 AOT 지원을 위한 LibraryImport 옵션
13465정성태11/28/202311818개발 환경 구성: 689. MSBuild - CopyToOutputDirectory가 "dotnet publish" 시에는 적용되지 않는 문제파일 다운로드1
13464정성태11/28/202311537닷넷: 2174. C# - .NET 7부터 UnmanagedCallersOnly 함수 export 기능을 AOT 빌드에 통합파일 다운로드1
13463정성태11/27/202310524오류 유형: 881. Visual Studio - NU1605: Warning As Error: Detected package downgrade
13462정성태11/27/202311315오류 유형: 880. Visual Studio - error CS0246: The type or namespace name '...' could not be found
13461정성태11/26/202311367닷넷: 2173. .NET Core 3/5+ 기반의 COM Server를 registry 등록 없이 사용하는 방법파일 다운로드1
13460정성태11/26/202311541닷넷: 2172. .NET 6+ 기반의 COM Server 내에 Type Library를 내장하는 방법파일 다운로드1
13459정성태11/26/202312172닷넷: 2171. .NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법파일 다운로드1
13458정성태11/26/202312278닷넷: 2170. .NET Core/5+ 기반의 COM Server를 tlb 파일을 생성하는 방법(tlbexp)
13457정성태11/25/202311721VS.NET IDE: 187. Visual Studio - 16.9 버전부터 추가된 "Display inline type hints" 옵션
13456정성태11/25/202312710닷넷: 2169. C# - OpenAI를 사용해 PDF 데이터를 대상으로 OpenAI 챗봇 작성 [1]파일 다운로드1
13455정성태11/25/202312228닷넷: 2168. C# - Azure.AI.OpenAI 패키지로 OpenAI 사용파일 다운로드1
13454정성태11/23/202312646닷넷: 2167. C# - Qdrant Vector DB를 이용한 Embedding 벡터 값 보관/조회 (Azure OpenAI) [1]파일 다운로드1
13453정성태11/23/202310504오류 유형: 879. docker desktop 설치 시 "Invalid JSON string. (Exception from HRESULT: 0x83750007)"
13452정성태11/22/202311257닷넷: 2166. C# - Azure OpenAI API를 이용해 사용자가 제공하는 정보를 대상으로 검색하는 방법파일 다운로드1
13451정성태11/21/202311362닷넷: 2165. C# - Azure OpenAI API를 이용해 ChatGPT처럼 동작하는 콘솔 응용 프로그램 제작파일 다운로드1
13450정성태11/21/202311261닷넷: 2164. C# - Octokit을 이용한 GitHub Issue 검색파일 다운로드1
13449정성태11/21/202311795개발 환경 구성: 688. Azure OpenAI 서비스 신청 방법
... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...