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

feat: add breadcrumb ringbuffer to avoid O(n) memmove #1060

Merged
merged 27 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1f6dd14
feat: initial ringbuffer implementation
JoshuaMoelans Oct 24, 2024
369da4e
chore: cleanup code
JoshuaMoelans Oct 24, 2024
55fdedc
chore: added todo
JoshuaMoelans Oct 24, 2024
8fd41b4
removed unnecessary buffer end value
JoshuaMoelans Oct 28, 2024
68d2352
changed buffer start_idx storage to [0]
JoshuaMoelans Oct 28, 2024
49ae90b
fixed issues in new storing method
JoshuaMoelans Oct 28, 2024
efbeaa1
updated test to new ringbuffer append logic
JoshuaMoelans Oct 28, 2024
8d0a142
refactor: renamed to sentry__value_append_ringbuffer
JoshuaMoelans Oct 28, 2024
a16bc80
test: removed old bounded append test
JoshuaMoelans Oct 28, 2024
03d320c
chore: update CHANGELOG.md
JoshuaMoelans Oct 28, 2024
0386111
Merge branch 'master' into feat/breadcrumb_ringbuffer_joshua
JoshuaMoelans Oct 28, 2024
a35a735
chore: update CHANGELOG.md
JoshuaMoelans Oct 28, 2024
29cf79d
chore: linting
JoshuaMoelans Oct 28, 2024
872b094
increase refcount of ringbuffer-to-list items
JoshuaMoelans Oct 29, 2024
2e174e0
apply suggestion from code review
JoshuaMoelans Oct 29, 2024
bf3f5a6
added ringbuffer test
JoshuaMoelans Oct 29, 2024
3776158
fixed ringbuffer to list conversion
JoshuaMoelans Oct 29, 2024
6081534
direct access to ringbuffer items
JoshuaMoelans Oct 29, 2024
38d1ef9
updated test with proper refcount check
JoshuaMoelans Oct 29, 2024
9c51975
removed unnecessary decref from test
JoshuaMoelans Oct 29, 2024
805dd04
added decref of temporary ringbuffer list
JoshuaMoelans Oct 29, 2024
3dc739a
removed double cloning
JoshuaMoelans Oct 29, 2024
0452b62
changing types from int32_t to size_t
JoshuaMoelans Oct 29, 2024
13fc770
moved declaration from public to private header
JoshuaMoelans Oct 29, 2024
2b56915
added decref
JoshuaMoelans Oct 29, 2024
751e8ff
type conversion
JoshuaMoelans Oct 29, 2024
190a4a5
applied suggestions from code review
JoshuaMoelans Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased


**Fixes**:

- Add breadcrumb ringbuffer to avoid O(n) memmove on adding more than max breadcrumbs ([#1060](https://github.com/getsentry/sentry-native/pull/1060))

## 0.7.11

**Fixes**:
Expand Down
2 changes: 1 addition & 1 deletion src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ sentry_add_breadcrumb(sentry_value_t breadcrumb)
// the `no_flush` will avoid triggering *both* scope-change and
// breadcrumb-add events.
SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) {
sentry__value_append_bounded(
sentry__value_append_ringbuffer(
scope->breadcrumbs, breadcrumb, max_breadcrumbs);
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/sentry_scope.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,10 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope,
sentry_value_decref(contexts);

if (mode & SENTRY_SCOPE_BREADCRUMBS) {
PLACE_CLONED_VALUE("breadcrumbs", scope->breadcrumbs);
sentry_value_t l
= sentry__value_ring_buffer_to_list(scope->breadcrumbs);
PLACE_VALUE("breadcrumbs", l);
sentry_value_decref(l);
}

if (mode & SENTRY_SCOPE_MODULES) {
Expand Down
73 changes: 51 additions & 22 deletions src/sentry_value.c
Original file line number Diff line number Diff line change
Expand Up @@ -629,40 +629,47 @@ sentry__value_clone(sentry_value_t value)
}
}

/**
* This appends `v` to the List `value`.
* To make this work properly as a ring buffer, the value list needs to have
* the ring buffer start index as the first element
* (e.g, 1 until max is exceeded, then it will update for each added item)
*
* It will remove the oldest value in the list, in case the total number of
* items would exceed `max`.
*
* The list is of size `max + 1` to store the start index.
*
* Returns 0 on success.
*/
int
sentry__value_append_bounded(sentry_value_t value, sentry_value_t v, size_t max)
sentry__value_append_ringbuffer(
sentry_value_t value, sentry_value_t v, size_t max)
{
thing_t *thing = value_as_unfrozen_thing(value);
if (!thing || thing_get_type(thing) != THING_TYPE_LIST) {
goto fail;
}

list_t *l = thing->payload._ptr;

if (l->len < max) {
if (l->len == 0) {
sentry_value_append(value, sentry_value_new_int32(1));
}
if (l->len < max + 1) {
return sentry_value_append(value, v);
}
if (l->len > max + 1) {
SENTRY_WARNF("Cannot reduce Ringbuffer list size from %d to %d.",
l->len - 1, max);
goto fail;
}
const int32_t start_idx = sentry_value_as_int32(l->items[0]);

// len: 120
// max: 100
// move to 0
// move 99 items (len - 1)
// from 20
sentry_value_decref(l->items[start_idx]);
l->items[start_idx] = v;
l->items[0] = sentry_value_new_int32((start_idx % (int32_t)max) + 1);

size_t to_move = max >= 1 ? max - 1 : 0;
size_t to_shift = l->len - to_move;
for (size_t i = 0; i < to_shift; i++) {
sentry_value_decref(l->items[i]);
}
if (to_move) {
memmove(l->items, l->items + to_shift, to_move * sizeof(l->items[0]));
}
if (max >= 1) {
l->items[max - 1] = v;
} else {
sentry_value_decref(v);
}
l->len = max;
l->len = max + 1;
return 0;

fail:
Expand Down Expand Up @@ -840,6 +847,28 @@ sentry_value_as_string(sentry_value_t value)
}
}

sentry_value_t
sentry__value_ring_buffer_to_list(const sentry_value_t rb)
{
const thing_t *thing = value_as_thing(rb);
if (!thing || thing_get_type(thing) != THING_TYPE_LIST) {
return sentry_value_new_null();
}
const list_t *rb_list = thing->payload._ptr;
if (rb_list->len == 0) {
return sentry_value_new_list();
}
const size_t start_idx = sentry_value_as_int32(rb_list->items[0]);

sentry_value_t rv = sentry_value_new_list();
for (size_t i = 0; i < rb_list->len - 1; i++) {
const size_t idx = (start_idx - 1 + i) % (rb_list->len - 1) + 1;
sentry_value_incref(rb_list->items[idx]);
sentry_value_append(rv, rb_list->items[idx]);
}
return rv;
}

int
sentry_value_is_true(sentry_value_t value)
{
Expand Down
15 changes: 12 additions & 3 deletions src/sentry_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,23 @@ sentry_value_t sentry__value_clone(sentry_value_t value);

/**
* This appends `v` to the List `value`.
* It will remove the first value of the list, is case the total number if items
* would exceed `max`.
*
* On non-full lists, exponentially reallocate space to accommodate new values
* (until we reach `max`). After reaching max, the oldest value is removed to
* make space for the new one.
*
* `max` should stay fixed over multiple invocations.
*
* Returns 0 on success.
*/
int sentry__value_append_bounded(
int sentry__value_append_ringbuffer(
sentry_value_t value, sentry_value_t v, size_t max);

/**
* Converts ring buffer to linear list
*/
sentry_value_t sentry__value_ring_buffer_to_list(sentry_value_t rb);

/**
* Deep-merges object src into dst.
*
Expand Down
114 changes: 54 additions & 60 deletions tests/unit/test_value.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,21 +176,68 @@ SENTRY_TEST(value_list)
sentry_value_decref(val);

val = sentry_value_new_list();
sentry_value_append(val, sentry_value_new_int32(1));
for (int32_t i = 1; i <= 10; i++) {
sentry_value_append(val, sentry_value_new_int32(i));
sentry__value_append_ringbuffer(val, sentry_value_new_int32(i), 5);
}
sentry__value_append_bounded(val, sentry_value_new_int32(1010), 5);
sentry__value_append_ringbuffer(val, sentry_value_new_int32(1010), 5);
#define CHECK_IDX(Idx, Val) \
TEST_CHECK_INT_EQUAL( \
sentry_value_as_int32(sentry_value_get_by_index(val, Idx)), Val)
CHECK_IDX(0, 7);
CHECK_IDX(1, 8);
CHECK_IDX(2, 9);
CHECK_IDX(3, 10);
CHECK_IDX(4, 1010);
CHECK_IDX(1, 1010);
CHECK_IDX(2, 7);
CHECK_IDX(3, 8);
CHECK_IDX(4, 9);
CHECK_IDX(5, 10);
sentry_value_decref(val);
}

SENTRY_TEST(value_ringbuffer)
{
sentry_value_t val = sentry_value_new_list();
sentry_value_append(val, sentry_value_new_int32(1)); // start index

const sentry_value_t v0 = sentry_value_new_object();
sentry_value_set_by_key(v0, "key", sentry_value_new_int32((int32_t)0));
const sentry_value_t v1 = sentry_value_new_object();
sentry_value_set_by_key(v1, "key", sentry_value_new_int32((int32_t)1));
const sentry_value_t v2 = sentry_value_new_object();
sentry_value_set_by_key(v2, "key", sentry_value_new_int32((int32_t)2));
const sentry_value_t v3 = sentry_value_new_object();
sentry_value_set_by_key(v3, "key", sentry_value_new_int32((int32_t)3));

sentry__value_append_ringbuffer(val, v0, 3);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v0), 1);
sentry_value_incref(v0);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v0), 2);

sentry__value_append_ringbuffer(val, v1, 3);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v1), 1);
sentry__value_append_ringbuffer(val, v2, 3);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v2), 1);
sentry__value_append_ringbuffer(val, v3, 3);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v3), 1);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v0), 1);

const sentry_value_t l = sentry__value_ring_buffer_to_list(val);
TEST_CHECK_INT_EQUAL(sentry_value_get_length(l), 3);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v3), 2);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v2), 2);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(v1), 2);
#define CHECK_KEY_IDX(List, Idx, Val) \
TEST_CHECK_INT_EQUAL(sentry_value_as_int32(sentry_value_get_by_key( \
sentry_value_get_by_index(List, Idx), "key")), \
Val)

CHECK_KEY_IDX(l, 0, 1);
CHECK_KEY_IDX(l, 1, 2);
CHECK_KEY_IDX(l, 2, 3);

sentry_value_decref(l);
sentry_value_decref(val);
sentry_value_decref(v0); // one manual incref
}

SENTRY_TEST(value_object)
{
sentry_value_t val = sentry_value_new_object();
Expand Down Expand Up @@ -509,59 +556,6 @@ SENTRY_TEST(value_wrong_type)
TEST_CHECK(sentry_value_get_length(val) == 0);
}

SENTRY_TEST(value_collections_leak)
JoshuaMoelans marked this conversation as resolved.
Show resolved Hide resolved
{
// decref the value correctly on error
sentry_value_t obj = sentry_value_new_object();
sentry_value_t null_v = sentry_value_new_null();

sentry_value_incref(obj);
sentry_value_set_by_key(null_v, "foo", obj);

sentry_value_incref(obj);
sentry_value_set_by_index(null_v, 123, obj);

sentry_value_incref(obj);
sentry_value_append(null_v, obj);

TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 1);

sentry_value_t list = sentry_value_new_list();

sentry_value_incref(obj);
sentry_value_append(list, obj);
sentry_value_incref(obj);
sentry_value_append(list, obj);
sentry_value_incref(obj);
sentry_value_append(list, obj);
sentry_value_incref(obj);
sentry_value_append(list, obj);
sentry_value_incref(obj);
sentry_value_append(list, obj);

// decref the existing values correctly on bounded append
sentry_value_incref(obj);
sentry__value_append_bounded(list, obj, 2);
sentry_value_incref(obj);
sentry__value_append_bounded(list, obj, 2);

TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 3);

sentry_value_incref(obj);
sentry__value_append_bounded(list, obj, 1);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 2);

sentry_value_incref(obj);
sentry__value_append_bounded(list, obj, 0);
TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 1);
TEST_CHECK_INT_EQUAL(sentry_value_get_length(list), 0);

sentry_value_decref(list);

TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 1);
sentry_value_decref(obj);
}

SENTRY_TEST(value_set_by_null_key)
{
sentry_value_t value = sentry_value_new_object();
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/tests.inc
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ XX(user_feedback_with_null_args)
XX(uuid_api)
XX(uuid_v4)
XX(value_bool)
XX(value_collections_leak)
XX(value_double)
XX(value_freezing)
XX(value_get_by_null_key)
Expand All @@ -132,6 +131,7 @@ XX(value_object)
XX(value_object_merge)
XX(value_object_merge_nested)
XX(value_remove_by_null_key)
XX(value_ringbuffer)
XX(value_set_by_null_key)
XX(value_set_stacktrace)
XX(value_string)
Expand Down
Loading