Skip to content

Commit

Permalink
[SVLS-5262] Span Pointer standard hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
apiarian-datadog committed Sep 17, 2024
1 parent 5ace397 commit 30ed9a8
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 0 deletions.
29 changes: 29 additions & 0 deletions ddtrace/_trace/_span_pointer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# A Private API for now.
from enum import Enum
from hashlib import sha256
from typing import Any
from typing import Dict
from typing import NamedTuple
Expand Down Expand Up @@ -54,3 +55,31 @@ def __init__(
def __post_init__(self):
# Do not want to do the trace_id and span_id checks that SpanLink does.
pass


class _StandardHashingRules:
# This is a bit of a strange class. Its instances behave like simple
# function. This allows us to do the hex_digits calculation once. It also
# allows us to write tests for the chosen base_hashing_function instead of
# having to add runtime sanity checks. This class should be instantiated
# only once, internally, and the result stored as a the
# _standard_hashing_function "function" below.

def __init__(self):
self.separator = b"|"

self.bits_per_hex_digit = 4
self.desired_bits = 128

self.hex_digits = self.desired_bits // self.bits_per_hex_digit

self.base_hashing_function = sha256

def __call__(self, *elements: bytes) -> str:
if not elements:
raise ValueError("elements must not be empty")

return self.base_hashing_function(self.separator.join(elements)).hexdigest()[: self.hex_digits]


_standard_hashing_function = _StandardHashingRules()
53 changes: 53 additions & 0 deletions tests/tracer/test_span_pointers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import List
from typing import NamedTuple

import pytest

from ddtrace._trace._span_pointer import _standard_hashing_function


class TestStandardHashingFunction:
class HashingCase(NamedTuple):
name: str
elements: List[bytes]
result: str

@pytest.mark.parametrize(
"test_case",
[
HashingCase(
name="one foo",
elements=[b"foo"],
result="2c26b46b68ffc68ff99b453c1d304134",
),
HashingCase(
name="foo and bar",
elements=[b"foo", b"bar"],
result="0fc7e25e075c7849f89b9729d1aeada1",
),
HashingCase(
name="bar and foo, order matters",
elements=[b"bar", b"foo"],
result="527f4b4996d63db7053fd4b376b782a3",
),
],
ids=lambda test_case: test_case.name,
)
def test_normal_hashing(self, test_case: HashingCase) -> None:
assert _standard_hashing_function(*test_case.elements) == test_case.result

def test_validate_hashing_rules(self) -> None:
hex_digits_per_byte = 2
desired_bytes = _standard_hashing_function.hex_digits // hex_digits_per_byte

bytes_in_digest = _standard_hashing_function.base_hashing_function().digest_size

assert bytes_in_digest >= desired_bytes

def test_hashing_requries_arguments(self) -> None:
with pytest.raises(ValueError, match="elements must not be empty"):
_standard_hashing_function()

def test_hashing_requires_bytes(self) -> None:
with pytest.raises(TypeError, match="expected a bytes-like object"):
_standard_hashing_function("foo")

0 comments on commit 30ed9a8

Please sign in to comment.