Skip to content

Commit 0f58706

Browse files
authored
ref: expose merge_breadcrumbs for crashpad offline caching (#1491)
1 parent fc47ea0 commit 0f58706

5 files changed

Lines changed: 248 additions & 116 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
@@ -1741,3 +1741,114 @@ sentry__value_from_msgpack(const char *buf, size_t buf_len)
17411741

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

src/sentry_value.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,11 @@ void sentry__value_add_attribute(sentry_value_t attributes,
125125
*/
126126
sentry_value_t sentry__value_from_msgpack(const char *buf, size_t buf_len);
127127

128+
/**
129+
* Merges two breadcrumb lists in timestamp order, keeping at most `max` items.
130+
* Returns a new list with the merged breadcrumbs.
131+
*/
132+
sentry_value_t sentry__value_merge_breadcrumbs(
133+
sentry_value_t list_a, sentry_value_t list_b, size_t max);
134+
128135
#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+
}

0 commit comments

Comments
 (0)