Skip to content

Commit 9a32cbf

Browse files
committed
ref: expose merge_breadcrumbs for crashpad offline caching (#1491)
1 parent b6ad0d8 commit 9a32cbf

5 files changed

Lines changed: 244 additions & 112 deletions

File tree

src/sentry_scope.c

Lines changed: 2 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -312,116 +312,6 @@ sentry__scope_get_span_or_transaction(void)
312312
}
313313
#endif
314314

315-
static int
316-
cmp_breadcrumb(sentry_value_t a, sentry_value_t b, bool *error)
317-
{
318-
sentry_value_t timestamp_a = sentry_value_get_by_key(a, "timestamp");
319-
sentry_value_t timestamp_b = sentry_value_get_by_key(b, "timestamp");
320-
if (sentry_value_is_null(timestamp_a)) {
321-
*error = true;
322-
return -1;
323-
}
324-
if (sentry_value_is_null(timestamp_b)) {
325-
*error = true;
326-
return 1;
327-
}
328-
329-
return strcmp(sentry_value_as_string(timestamp_a),
330-
sentry_value_as_string(timestamp_b));
331-
}
332-
333-
static bool
334-
append_breadcrumb(sentry_value_t target, sentry_value_t source, size_t index)
335-
{
336-
int rv = sentry_value_append(
337-
target, sentry_value_get_by_index_owned(source, index));
338-
if (rv != 0) {
339-
SENTRY_ERROR("Failed to merge breadcrumbs");
340-
sentry_value_decref(target);
341-
return false;
342-
}
343-
return true;
344-
}
345-
346-
static sentry_value_t
347-
merge_breadcrumbs(sentry_value_t list_a, sentry_value_t list_b, size_t max)
348-
{
349-
size_t len_a = sentry_value_get_type(list_a) == SENTRY_VALUE_TYPE_LIST
350-
? sentry_value_get_length(list_a)
351-
: 0;
352-
size_t len_b = sentry_value_get_type(list_b) == SENTRY_VALUE_TYPE_LIST
353-
? sentry_value_get_length(list_b)
354-
: 0;
355-
356-
if (len_a == 0 && len_b == 0) {
357-
return sentry_value_new_null();
358-
} else if (len_a == 0) {
359-
sentry_value_incref(list_b);
360-
return list_b;
361-
} else if (len_b == 0) {
362-
sentry_value_incref(list_a);
363-
return list_a;
364-
}
365-
366-
bool error = false;
367-
size_t idx_a = 0;
368-
size_t idx_b = 0;
369-
size_t total = len_a + len_b;
370-
size_t skip = total > max ? total - max : 0;
371-
sentry_value_t result = sentry__value_new_list_with_size(total - skip);
372-
373-
// skip oldest breadcrumbs to fit max
374-
while (idx_a < len_a && idx_b < len_b && idx_a + idx_b < skip) {
375-
sentry_value_t item_a = sentry_value_get_by_index(list_a, idx_a);
376-
sentry_value_t item_b = sentry_value_get_by_index(list_b, idx_b);
377-
378-
if (cmp_breadcrumb(item_a, item_b, &error) <= 0) {
379-
idx_a++;
380-
} else {
381-
idx_b++;
382-
}
383-
}
384-
while (idx_a < len_a && idx_a + idx_b < skip) {
385-
idx_a++;
386-
}
387-
while (idx_b < len_b && idx_a + idx_b < skip) {
388-
idx_b++;
389-
}
390-
391-
// merge the remaining breadcrumbs in timestamp order
392-
while (idx_a < len_a && idx_b < len_b) {
393-
sentry_value_t item_a = sentry_value_get_by_index(list_a, idx_a);
394-
sentry_value_t item_b = sentry_value_get_by_index(list_b, idx_b);
395-
396-
if (cmp_breadcrumb(item_a, item_b, &error) <= 0) {
397-
if (!append_breadcrumb(result, list_a, idx_a++)) {
398-
return sentry_value_new_null();
399-
}
400-
} else {
401-
if (!append_breadcrumb(result, list_b, idx_b++)) {
402-
return sentry_value_new_null();
403-
}
404-
}
405-
}
406-
while (idx_a < len_a) {
407-
if (!append_breadcrumb(result, list_a, idx_a++)) {
408-
return sentry_value_new_null();
409-
}
410-
}
411-
while (idx_b < len_b) {
412-
if (!append_breadcrumb(result, list_b, idx_b++)) {
413-
return sentry_value_new_null();
414-
}
415-
}
416-
417-
if (error) {
418-
SENTRY_WARN("Detected missing timestamps while merging breadcrumbs. "
419-
"This may lead to unexpected results.");
420-
}
421-
422-
return result;
423-
}
424-
425315
void
426316
sentry__scope_apply_to_event(const sentry_scope_t *scope,
427317
const sentry_options_t *options, sentry_value_t event,
@@ -522,8 +412,8 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope,
522412
sentry_value_t scope_breadcrumbs
523413
= sentry__ringbuffer_to_list(scope->breadcrumbs);
524414
sentry_value_set_by_key(event, "breadcrumbs",
525-
merge_breadcrumbs(event_breadcrumbs, scope_breadcrumbs,
526-
options->max_breadcrumbs));
415+
sentry__value_merge_breadcrumbs(event_breadcrumbs,
416+
scope_breadcrumbs, options->max_breadcrumbs));
527417
sentry_value_decref(scope_breadcrumbs);
528418
}
529419

src/sentry_value.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,3 +1725,114 @@ sentry__value_from_msgpack(const char *buf, size_t buf_len)
17251725

17261726
return result;
17271727
}
1728+
1729+
static int
1730+
cmp_breadcrumb(sentry_value_t a, sentry_value_t b, bool *error)
1731+
{
1732+
sentry_value_t timestamp_a = sentry_value_get_by_key(a, "timestamp");
1733+
sentry_value_t timestamp_b = sentry_value_get_by_key(b, "timestamp");
1734+
if (sentry_value_is_null(timestamp_a)) {
1735+
*error = true;
1736+
return -1;
1737+
}
1738+
if (sentry_value_is_null(timestamp_b)) {
1739+
*error = true;
1740+
return 1;
1741+
}
1742+
1743+
return strcmp(sentry_value_as_string(timestamp_a),
1744+
sentry_value_as_string(timestamp_b));
1745+
}
1746+
1747+
static bool
1748+
append_breadcrumb(sentry_value_t target, sentry_value_t source, size_t index)
1749+
{
1750+
int rv = sentry_value_append(
1751+
target, sentry_value_get_by_index_owned(source, index));
1752+
if (rv != 0) {
1753+
SENTRY_ERROR("Failed to merge breadcrumbs");
1754+
sentry_value_decref(target);
1755+
return false;
1756+
}
1757+
return true;
1758+
}
1759+
1760+
sentry_value_t
1761+
sentry__value_merge_breadcrumbs(
1762+
sentry_value_t list_a, sentry_value_t list_b, size_t max)
1763+
{
1764+
size_t len_a = sentry_value_get_type(list_a) == SENTRY_VALUE_TYPE_LIST
1765+
? sentry_value_get_length(list_a)
1766+
: 0;
1767+
size_t len_b = sentry_value_get_type(list_b) == SENTRY_VALUE_TYPE_LIST
1768+
? sentry_value_get_length(list_b)
1769+
: 0;
1770+
1771+
if (len_a == 0 && len_b == 0) {
1772+
return sentry_value_new_null();
1773+
} else if (len_a == 0) {
1774+
sentry_value_incref(list_b);
1775+
return list_b;
1776+
} else if (len_b == 0) {
1777+
sentry_value_incref(list_a);
1778+
return list_a;
1779+
}
1780+
1781+
bool error = false;
1782+
size_t idx_a = 0;
1783+
size_t idx_b = 0;
1784+
size_t total = len_a + len_b;
1785+
size_t skip = total > max ? total - max : 0;
1786+
sentry_value_t result = sentry__value_new_list_with_size(total - skip);
1787+
1788+
// skip oldest breadcrumbs to fit max
1789+
while (idx_a < len_a && idx_b < len_b && idx_a + idx_b < skip) {
1790+
sentry_value_t item_a = sentry_value_get_by_index(list_a, idx_a);
1791+
sentry_value_t item_b = sentry_value_get_by_index(list_b, idx_b);
1792+
1793+
if (cmp_breadcrumb(item_a, item_b, &error) <= 0) {
1794+
idx_a++;
1795+
} else {
1796+
idx_b++;
1797+
}
1798+
}
1799+
while (idx_a < len_a && idx_a + idx_b < skip) {
1800+
idx_a++;
1801+
}
1802+
while (idx_b < len_b && idx_a + idx_b < skip) {
1803+
idx_b++;
1804+
}
1805+
1806+
// merge the remaining breadcrumbs in timestamp order
1807+
while (idx_a < len_a && idx_b < len_b) {
1808+
sentry_value_t item_a = sentry_value_get_by_index(list_a, idx_a);
1809+
sentry_value_t item_b = sentry_value_get_by_index(list_b, idx_b);
1810+
1811+
if (cmp_breadcrumb(item_a, item_b, &error) <= 0) {
1812+
if (!append_breadcrumb(result, list_a, idx_a++)) {
1813+
return sentry_value_new_null();
1814+
}
1815+
} else {
1816+
if (!append_breadcrumb(result, list_b, idx_b++)) {
1817+
return sentry_value_new_null();
1818+
}
1819+
}
1820+
}
1821+
while (idx_a < len_a) {
1822+
if (!append_breadcrumb(result, list_a, idx_a++)) {
1823+
return sentry_value_new_null();
1824+
}
1825+
}
1826+
while (idx_b < len_b) {
1827+
if (!append_breadcrumb(result, list_b, idx_b++)) {
1828+
return sentry_value_new_null();
1829+
}
1830+
}
1831+
1832+
if (error) {
1833+
SENTRY_WARN("Detected missing timestamps while merging breadcrumbs. "
1834+
"This may lead to unexpected results.");
1835+
}
1836+
1837+
return result;
1838+
}

src/sentry_value.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,11 @@ void sentry__jsonwriter_write_value(
117117
*/
118118
sentry_value_t sentry__value_from_msgpack(const char *buf, size_t buf_len);
119119

120+
/**
121+
* Merges two breadcrumb lists in timestamp order, keeping at most `max` items.
122+
* Returns a new list with the merged breadcrumbs.
123+
*/
124+
sentry_value_t sentry__value_merge_breadcrumbs(
125+
sentry_value_t list_a, sentry_value_t list_b, size_t max);
126+
120127
#endif

tests/unit/test_value.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
#include <stdint.h>
77
#include <string.h>
88

9+
static sentry_value_t
10+
breadcrumb_with_ts(const char *message, const char *timestamp)
11+
{
12+
sentry_value_t breadcrumb = sentry_value_new_breadcrumb(NULL, message);
13+
sentry_value_set_by_key(
14+
breadcrumb, "timestamp", sentry_value_new_string(timestamp));
15+
return breadcrumb;
16+
}
17+
918
SENTRY_TEST(value_null)
1019
{
1120
sentry_value_t val = sentry_value_new_null();
@@ -1564,3 +1573,114 @@ SENTRY_TEST(value_from_msgpack_flat_buffer)
15641573
sentry_value_decref(val3);
15651574
sentry_value_decref(result);
15661575
}
1576+
1577+
#define TEST_CHECK_MESSAGE_EQUAL(breadcrumbs, index, message) \
1578+
TEST_CHECK_STRING_EQUAL( \
1579+
sentry_value_as_string(sentry_value_get_by_key( \
1580+
sentry_value_get_by_index(breadcrumbs, index), "message")), \
1581+
message)
1582+
1583+
SENTRY_TEST(value_merge_breadcrumbs_both_empty)
1584+
{
1585+
sentry_value_t list_a = sentry_value_new_list();
1586+
sentry_value_t list_b = sentry_value_new_list();
1587+
1588+
sentry_value_t result = sentry__value_merge_breadcrumbs(list_a, list_b, 10);
1589+
TEST_CHECK(sentry_value_is_null(result));
1590+
1591+
sentry_value_decref(list_a);
1592+
sentry_value_decref(list_b);
1593+
}
1594+
1595+
SENTRY_TEST(value_merge_breadcrumbs_one_empty)
1596+
{
1597+
sentry_value_t list_a = sentry_value_new_list();
1598+
sentry_value_append(
1599+
list_a, breadcrumb_with_ts("a1", "2024-01-01T00:00:01"));
1600+
sentry_value_append(
1601+
list_a, breadcrumb_with_ts("a2", "2024-01-01T00:00:02"));
1602+
sentry_value_t list_b = sentry_value_new_list();
1603+
1604+
// list_b is empty -> return list_a
1605+
sentry_value_t result = sentry__value_merge_breadcrumbs(list_a, list_b, 10);
1606+
TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST);
1607+
TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 2);
1608+
TEST_CHECK_MESSAGE_EQUAL(result, 0, "a1");
1609+
TEST_CHECK_MESSAGE_EQUAL(result, 1, "a2");
1610+
sentry_value_decref(result);
1611+
1612+
// list_a is empty -> return list_b
1613+
sentry_value_t list_c = sentry_value_new_list();
1614+
result = sentry__value_merge_breadcrumbs(list_c, list_a, 10);
1615+
TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST);
1616+
TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 2);
1617+
TEST_CHECK_MESSAGE_EQUAL(result, 0, "a1");
1618+
TEST_CHECK_MESSAGE_EQUAL(result, 1, "a2");
1619+
sentry_value_decref(result);
1620+
1621+
sentry_value_decref(list_a);
1622+
sentry_value_decref(list_b);
1623+
sentry_value_decref(list_c);
1624+
}
1625+
1626+
SENTRY_TEST(value_merge_breadcrumbs_interleaved)
1627+
{
1628+
sentry_value_t list_a = sentry_value_new_list();
1629+
sentry_value_append(
1630+
list_a, breadcrumb_with_ts("a1", "2024-01-01T00:00:01"));
1631+
sentry_value_append(
1632+
list_a, breadcrumb_with_ts("a4", "2024-01-01T00:00:04"));
1633+
1634+
sentry_value_t list_b = sentry_value_new_list();
1635+
sentry_value_append(
1636+
list_b, breadcrumb_with_ts("b2", "2024-01-01T00:00:02"));
1637+
sentry_value_append(
1638+
list_b, breadcrumb_with_ts("b3", "2024-01-01T00:00:03"));
1639+
1640+
sentry_value_t result = sentry__value_merge_breadcrumbs(list_a, list_b, 10);
1641+
TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST);
1642+
TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 4);
1643+
TEST_CHECK_MESSAGE_EQUAL(result, 0, "a1");
1644+
TEST_CHECK_MESSAGE_EQUAL(result, 1, "b2");
1645+
TEST_CHECK_MESSAGE_EQUAL(result, 2, "b3");
1646+
TEST_CHECK_MESSAGE_EQUAL(result, 3, "a4");
1647+
1648+
sentry_value_decref(result);
1649+
sentry_value_decref(list_a);
1650+
sentry_value_decref(list_b);
1651+
}
1652+
1653+
SENTRY_TEST(value_merge_breadcrumbs_max_limit)
1654+
{
1655+
sentry_value_t list_a = sentry_value_new_list();
1656+
sentry_value_append(
1657+
list_a, breadcrumb_with_ts("a1", "2024-01-01T00:00:01"));
1658+
sentry_value_append(
1659+
list_a, breadcrumb_with_ts("a3", "2024-01-01T00:00:03"));
1660+
1661+
sentry_value_t list_b = sentry_value_new_list();
1662+
sentry_value_append(
1663+
list_b, breadcrumb_with_ts("b2", "2024-01-01T00:00:02"));
1664+
sentry_value_append(
1665+
list_b, breadcrumb_with_ts("b4", "2024-01-01T00:00:04"));
1666+
1667+
// max=3 -> oldest (a1) should be dropped
1668+
sentry_value_t result = sentry__value_merge_breadcrumbs(list_a, list_b, 3);
1669+
TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST);
1670+
TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 3);
1671+
TEST_CHECK_MESSAGE_EQUAL(result, 0, "b2");
1672+
TEST_CHECK_MESSAGE_EQUAL(result, 1, "a3");
1673+
TEST_CHECK_MESSAGE_EQUAL(result, 2, "b4");
1674+
sentry_value_decref(result);
1675+
1676+
// max=2 -> oldest two (a1, b2) should be dropped
1677+
result = sentry__value_merge_breadcrumbs(list_a, list_b, 2);
1678+
TEST_CHECK(sentry_value_get_type(result) == SENTRY_VALUE_TYPE_LIST);
1679+
TEST_CHECK_INT_EQUAL(sentry_value_get_length(result), 2);
1680+
TEST_CHECK_MESSAGE_EQUAL(result, 0, "a3");
1681+
TEST_CHECK_MESSAGE_EQUAL(result, 1, "b4");
1682+
sentry_value_decref(result);
1683+
1684+
sentry_value_decref(list_a);
1685+
sentry_value_decref(list_b);
1686+
}

tests/unit/tests.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ XX(value_json_locales)
226226
XX(value_json_parsing)
227227
XX(value_json_surrogates)
228228
XX(value_list)
229+
XX(value_merge_breadcrumbs_both_empty)
230+
XX(value_merge_breadcrumbs_interleaved)
231+
XX(value_merge_breadcrumbs_max_limit)
232+
XX(value_merge_breadcrumbs_one_empty)
229233
XX(value_null)
230234
XX(value_object)
231235
XX(value_object_merge)

0 commit comments

Comments
 (0)