|
| 1 | +CREATE OR REPLACE FUNCTION gecko_trace.calculate_signature(rootSpan JSON) |
| 2 | +RETURNS STRING |
| 3 | +LANGUAGE js AS r""" |
| 4 | + // cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated. |
| 5 | + // A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance. |
| 6 | + // Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. |
| 7 | + // See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480 |
| 8 | + // https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js |
| 9 | + const cyrb64 = (str, seed = 0) => { |
| 10 | + let h1 = 0xdeadbeef ^ seed, |
| 11 | + h2 = 0x41c6ce57 ^ seed; |
| 12 | + for (let i = 0, ch; i < str.length; i++) { |
| 13 | + ch = str.charCodeAt(i); |
| 14 | + h1 = Math.imul(h1 ^ ch, 2654435761); |
| 15 | + h2 = Math.imul(h2 ^ ch, 1597334677); |
| 16 | + } |
| 17 | + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); |
| 18 | + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); |
| 19 | + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); |
| 20 | + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); |
| 21 | + // For a single 53-bit numeric return value we could return |
| 22 | + // 4294967296 * (2097151 & h2) + (h1 >>> 0); |
| 23 | + // but we instead return the full 64-bit value: |
| 24 | + return [h2 >>> 0, h1 >>> 0]; |
| 25 | + }; |
| 26 | +
|
| 27 | + const seed = 0; |
| 28 | + let digest = ""; |
| 29 | + const hash = (str) => { |
| 30 | + const [h2, h1] = cyrb64(digest + str, seed); |
| 31 | + digest = |
| 32 | + h2.toString(36).padStart(7, "0") + h1.toString(36).padStart(7, "0"); |
| 33 | + }; |
| 34 | +
|
| 35 | + const ATTRS_TO_SKIP = {"gecko_process_internal_id": null} |
| 36 | + const hashAttrs = (attrs) => { |
| 37 | + for (const [key, value] of Object.entries(attrs)) { |
| 38 | + if (key in ATTRS_TO_SKIP) continue; |
| 39 | + hash(key); |
| 40 | + hash(value); |
| 41 | + } |
| 42 | + } |
| 43 | +
|
| 44 | + const hashEvents = (events) => { |
| 45 | + for (const event of events) { |
| 46 | + hash(event.name); |
| 47 | + hashAttrs(event.attributes); |
| 48 | + } |
| 49 | + }; |
| 50 | +
|
| 51 | + const stack = [rootSpan]; |
| 52 | + while (stack.length > 0) { |
| 53 | + const span = stack.pop(); |
| 54 | + hashAttrs(span.resource.attributes); |
| 55 | + hash(span.scope.name); |
| 56 | + hash(span.name); |
| 57 | + if (span.events) { |
| 58 | + hashEvents(span.events); |
| 59 | + } |
| 60 | + stack.push(...span.childSpans); |
| 61 | + } |
| 62 | +
|
| 63 | + return digest; |
| 64 | +"""; |
| 65 | + |
| 66 | +-- Tests |
| 67 | +SELECT |
| 68 | + -- Test with simple root span |
| 69 | + assert.not_null( |
| 70 | + gecko_trace.calculate_signature( |
| 71 | + JSON '{"span_id": "root", "name": "test", "scope": {"name": "test_scope"}, "resource": {"attributes": {}}, "childSpans": []}' |
| 72 | + ) |
| 73 | + ), |
| 74 | + -- Test that same input produces same signature |
| 75 | + assert.equals( |
| 76 | + gecko_trace.calculate_signature( |
| 77 | + JSON '{"span_id": "root", "name": "test", "scope": {"name": "test_scope"}, "resource": {"attributes": {}}, "childSpans": []}' |
| 78 | + ), |
| 79 | + gecko_trace.calculate_signature( |
| 80 | + JSON '{"span_id": "root", "name": "test", "scope": {"name": "test_scope"}, "resource": {"attributes": {}}, "childSpans": []}' |
| 81 | + ) |
| 82 | + ), |
| 83 | + -- Test that null input returns empty string |
| 84 | + assert.equals("", gecko_trace.calculate_signature(NULL)); |
0 commit comments