Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map batch API #374

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 39 additions & 30 deletions map-low.go
Expand Up @@ -318,10 +318,12 @@ func (m *BPFMapLow) DeleteKey(key unsafe.Pointer) error {
// BPFMapLow Batch Operations
//

func (m *BPFMapLow) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// GetValueBatch gets the values with the given keys from the map.
// It returns the values and the number of read elements.
func (m *BPFMapLow) GetValueBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
valueSize, err := calcMapValueSize(m.ValueSize(), m.Type())
if err != nil {
return nil, fmt.Errorf("map %s %w", m.Name(), err)
return nil, 0, fmt.Errorf("map %s %w", m.Name(), err)
}

var (
Expand All @@ -332,7 +334,7 @@ func (m *BPFMapLow) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return nil, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return nil, 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -353,19 +355,22 @@ func (m *BPFMapLow) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 && errno != syscall.ENOENT {
return nil, fmt.Errorf("failed to batch get value %v in map %s: %w", keys, m.Name(), errno)
// retC < 0 && errno == syscall.ENOENT indicates a partial read.
if retC < 0 && (errno != syscall.ENOENT || countC == 0) {
return nil, 0, fmt.Errorf("failed to batch get value %v in map %s: %w", keys, m.Name(), errno)
}

// Either some or all entries were read.
// retC < 0 && errno == syscall.ENOENT indicates a partial read.
return collectBatchValues(values, uint32(countC), valueSize), nil
// Either some or all elements were read.
return collectBatchValues(values, uint32(countC), valueSize), uint32(countC), nil
}

func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// GetValueAndDeleteBatch gets the values with the given keys from the map and
// deletes them.
// It returns the values and the number of deleted elements.
func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
valueSize, err := calcMapValueSize(m.ValueSize(), m.Type())
if err != nil {
return nil, fmt.Errorf("map %s %w", m.Name(), err)
return nil, 0, fmt.Errorf("map %s %w", m.Name(), err)
}

var (
Expand All @@ -376,7 +381,7 @@ func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointe

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return nil, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return nil, 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -390,21 +395,23 @@ func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointe
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 && errno != syscall.ENOENT {
return nil, fmt.Errorf("failed to batch lookup and delete values %v in map %s: %w", keys, m.Name(), errno)
// retC < 0 && errno == syscall.ENOENT indicates a partial read and delete.
if retC < 0 && (errno != syscall.ENOENT || countC == 0) {
return nil, 0, fmt.Errorf("failed to batch lookup and delete values %v in map %s: %w", keys, m.Name(), errno)
}

// Either some or all entries were read and deleted.
// retC < 0 && errno == syscall.ENOENT indicates a partial read and delete.
return collectBatchValues(values, uint32(countC), valueSize), nil
// Either some or all elements were read and deleted.
return collectBatchValues(values, uint32(countC), valueSize), uint32(countC), nil
}

func (m *BPFMapLow) UpdateBatch(keys, values unsafe.Pointer, count uint32) error {
// UpdateBatch updates the elements with the given keys and values in the map.
// It returns the number of updated elements.
func (m *BPFMapLow) UpdateBatch(keys, values unsafe.Pointer, count uint32) (uint32, error) {
countC := C.uint(count)

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -416,22 +423,23 @@ func (m *BPFMapLow) UpdateBatch(keys, values unsafe.Pointer, count uint32) error
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 {
if errno != syscall.EFAULT && uint32(countC) != count {
return fmt.Errorf("failed to update ALL elements in map %s, updated (%d/%d): %w", m.Name(), uint32(countC), count, errno)
}
return fmt.Errorf("failed to batch update elements in map %s: %w", m.Name(), errno)
// retC < 0 && errno == syscall.E2BIG indicates a partial update.
geyslan marked this conversation as resolved.
Show resolved Hide resolved
if retC < 0 && (errno != syscall.E2BIG || countC == 0) {
geyslan marked this conversation as resolved.
Show resolved Hide resolved
return 0, fmt.Errorf("failed to batch update values %v in map %s: %w", keys, m.Name(), errno)
}

return nil
// Either some or all elements were updated.
return uint32(countC), nil
}

func (m *BPFMapLow) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error {
// DeleteKeyBatch deletes the elements with the given keys from the map.
// It returns the number of deleted elements.
func (m *BPFMapLow) DeleteKeyBatch(keys unsafe.Pointer, count uint32) (uint32, error) {
countC := C.uint(count)

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -442,12 +450,13 @@ func (m *BPFMapLow) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error {
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 && errno != syscall.ENOENT {
return fmt.Errorf("failed to batch delete keys %v in map %s: %w", keys, m.Name(), errno)
// retC < 0 && errno == syscall.ENOENT indicates a partial deletion.
if retC < 0 && (errno != syscall.ENOENT || countC == 0) {
return 0, fmt.Errorf("failed to batch delete keys %v in map %s: %w", keys, m.Name(), errno)
}

// retC < 0 && errno == syscall.ENOENT indicates a partial deletion.
return nil
// Either some or all elements were deleted.
return uint32(countC), nil
}

func collectBatchValues(values []byte, count uint32, valueSize int) [][]byte {
Expand Down
69 changes: 42 additions & 27 deletions map.go
Expand Up @@ -492,7 +492,6 @@ func (m *BPFMap) DeleteKey(key unsafe.Pointer) error {
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be populated with the keys returned from this operation.
// It returns the associated values as a slice of slices of bytes.
//
// This API allows for batch lookups of multiple keys, potentially in steps over
// multiple iterations. For example, you provide the last key seen (or nil) for
Expand All @@ -501,14 +500,16 @@ func (m *BPFMap) DeleteKey(key unsafe.Pointer) error {
// previous iteration as the startKey for the next iteration and repeat until
// nextKey is nil.
//
// The last argument, count, is the number of keys to lookup. The kernel will
// update it with the count of the elements that were retrieved.
// The last argument, count, is the number of keys to lookup.
//
// The API can return partial results even though an -1 is returned. In this case,
// errno will be set to `ENOENT` and the values slice and count will be filled in
// with the elements that were read. See the comment in `BPFMapLow.GetValueBatch`
// for more context.
func (m *BPFMap) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// It returns the associated values as a slice of slices of bytes and the number
// of elements that were retrieved.
//
// The API can return partial results even though the underlying logic received -1.
// In this case, no error will be returned. For checking if the returned values
// are partial, you can compare the number of elements returned with the passed
// count. See the comment in `BPFMapLow.GetValueBatch` for more context.
func (m *BPFMap) GetValueBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
return m.bpfMapLow.GetValueBatch(keys, startKey, nextKey, count)
}

Expand All @@ -517,7 +518,6 @@ func (m *BPFMap) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Poi
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be populated with the keys returned from this operation.
// It returns the associated values as a slice of slices of bytes.
//
// This API allows for batch lookups and deletion of multiple keys, potentially
// in steps over multiple iterations. For example, you provide the last key seen
Expand All @@ -527,14 +527,16 @@ func (m *BPFMap) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Poi
// previous iteration as the startKey for the next iteration and repeat until
// nextKey is nil.
//
// The last argument, count, is the number of keys to lookup and delete. The kernel
// will update it with the count of the elements that were retrieved and deleted.
// The last argument, count, is the number of keys to lookup and delete.
//
// It returns the associated values as a slice of slices of bytes and the number
// of elements that were retrieved and deleted.
//
// The API can return partial results even though an -1 is returned. In this case,
// errno will be set to `ENOENT` and the values slice and count will be filled in
// with the elements that were read. See the comment in `BPFMapLow.GetValueBatch`
// for more context.
func (m *BPFMap) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// The API can return partial results even though the underlying logic received -1.
// In this case, no error will be returned. For checking if the returned values
// are partial, you can compare the number of elements returned with the passed
// count. See the comment in `BPFMapLow.GetValueBatch` for more context.
func (m *BPFMap) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
return m.bpfMapLow.GetValueAndDeleteBatch(keys, startKey, nextKey, count)
}

Expand All @@ -543,22 +545,35 @@ func (m *BPFMap) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer,
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be updated using the second argument, values.
// It returns the associated error if any occurred.
//
// The last argument, count, is the number of keys to update. Passing an argument
// that greater than the number of keys in the map will cause the function to
// return a syscall.EPERM as an error.
func (m *BPFMap) UpdateBatch(keys, values unsafe.Pointer, count uint32) error {
// The last argument, count, is the number of keys to update.
//
// It returns the number of elements that were updated.
//
// The API can update fewer elements than requested even though the underlying
// logic received -1. This can happen if the map is full and the update operation
// fails for some of the keys. In this case, no error will be returned. For
// checking if the updated values are partial, you can compare the number of
// elements returned with the passed count. See the comment in
// `BPFMapLow.GetValueBatch` and `BPFMapLow.UpdateBatch` for more context.
func (m *BPFMap) UpdateBatch(keys, values unsafe.Pointer, count uint32) (uint32, error) {
return m.bpfMapLow.UpdateBatch(keys, values, count)
}

// DeleteKeyBatch allows for batch deletion of multiple elements in the map.
// DeleteKeyBatch deletes multiple elements from the map by specified keys.
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be deleted.
//
// The last argument, count, is the number of keys to delete.
//
// It returns the number of elements that were deleted.
//
// `count` number of keys will be deleted from the map. Passing an argument that
// greater than the number of keys in the map will cause the function to delete
// fewer keys than requested. See the comment in `BPFMapLow.GetValueBatch`
// for more context.
func (m *BPFMap) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error {
// The API can delete fewer elements than requested even though the underlying
// logic received -1. For checking if the deleted elements are partial, you can
// compare the number of elements returned with the passed count. See the comment
// in `BPFMapLow.GetValueBatch` for more context.
func (m *BPFMap) DeleteKeyBatch(keys unsafe.Pointer, count uint32) (uint32, error) {
return m.bpfMapLow.DeleteKeyBatch(keys, count)
}

Expand Down
2 changes: 1 addition & 1 deletion selftest/map-batch/main.bpf.c
Expand Up @@ -8,7 +8,7 @@ struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, u32);
__uint(max_entries, 1 << 24);
__uint(max_entries, 4);
} tester SEC(".maps");

char LICENSE[] SEC("license") = "Dual BSD/GPL";