Adds Tracer Side SQL Query Obfuscator#2498
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
kevingosse
left a comment
There was a problem hiding this comment.
I haven't fully reviewed the code yet. I think it needs to be rewritten to directly manipulate the chars rather than encoding to UTF-8 bytes.
| char[] byteArray1 = new char[13] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' }; | ||
| char[] byteArray2 = new char[4] { ',', '(', ')', '|' }; | ||
|
|
||
| for (int i = 0; i < byteArray1.Length; ++i) | ||
| { | ||
| int unsigned = byteArray1[i] & 0xFF; | ||
| numericLiteralPrefix.Set(unsigned, true); | ||
| } | ||
|
|
||
| for (int i = 0; i < byteArray2.Length; ++i) | ||
| { | ||
| int unsigned = byteArray2[i] & 0xFF; | ||
| splitters.Set(unsigned, true); | ||
| } |
There was a problem hiding this comment.
the &0xFF is needed in the Java version of the code because java bytes are signed, but this is not needed here (char is unsigned).
Also, there is an implicit cast from char to int.
Finally, you shouldn't name them "byteArray" since they're char arrays 😉
| char[] byteArray1 = new char[13] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' }; | |
| char[] byteArray2 = new char[4] { ',', '(', ')', '|' }; | |
| for (int i = 0; i < byteArray1.Length; ++i) | |
| { | |
| int unsigned = byteArray1[i] & 0xFF; | |
| numericLiteralPrefix.Set(unsigned, true); | |
| } | |
| for (int i = 0; i < byteArray2.Length; ++i) | |
| { | |
| int unsigned = byteArray2[i] & 0xFF; | |
| splitters.Set(unsigned, true); | |
| } | |
| var charArray1 = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' }; | |
| var charArray2 = new[] { ',', '(', ')', '|' }; | |
| foreach (var c in charArray1) | |
| { | |
| numericLiteralPrefix.Set(c, true); | |
| } | |
| foreach (var c in charArray2) | |
| { | |
| splitters.Set(c, true); | |
| } |
| private static BitArray numericLiteralPrefix = new BitArray(255, false); | ||
| private static BitArray splitters = new BitArray(255, false); |
There was a problem hiding this comment.
I believe the size should be 256. You may encounter all values from 0 to 255 inclusive, so that's 256 different values.
| private static BitArray numericLiteralPrefix = new BitArray(255, false); | |
| private static BitArray splitters = new BitArray(255, false); | |
| private static BitArray numericLiteralPrefix = new BitArray(256, false); | |
| private static BitArray splitters = new BitArray(256, false); |
| // return UTF8BytesString.create(sql, utf8); | ||
| Console.WriteLine("sql: " + sql + "utf8: " + utf8); | ||
|
|
There was a problem hiding this comment.
| // return UTF8BytesString.create(sql, utf8); | |
| Console.WriteLine("sql: " + sql + "utf8: " + utf8); |
| if (utf8 is null) | ||
| { | ||
| return null; | ||
| } | ||
| else | ||
| { | ||
| return sql; | ||
| } |
There was a problem hiding this comment.
utf8 cannot be null
| if (utf8 is null) | |
| { | |
| return null; | |
| } | |
| else | |
| { | |
| return sql; | |
| } | |
| return sql; |
|
|
||
| public static string SqlObfuscator(string sql) | ||
| { | ||
| var utf8 = Encoding.GetBytes(sql); |
There was a problem hiding this comment.
The Java version of the code applies the filtering on the UTF8 bytes because it needs to return an UTF8BytesString (so it saves one encoding step).
Here you're returning a string, so encoding the string is just adding extra overhead. You should instead convert the string to a char array:
var str = sql.ToCharArray();
Then apply the same algorithm on the char array. Later we can even add extra logic to have that array allocated lazily
There was a problem hiding this comment.
Hey @kevingosse thank you for the comments! In my lasted commit you can see the changes I've applied:
c6fc4e4
One thing to note is that for the IsSplitter function I end up adding a logic to return false if converting the char to bye/int leads to a value larger than the index of the array it is supposed to for the character in.
When I used to do the encoding to byte the Kanji characters there were converted to 3 different bytes [231, 189, 191] representing it, but now that we manipulate the Kanji character directly when converted to byte/int the result is [32639] and that would lead to a IndexOutOfRangeException for which I simply added the if to prevent using this large index numbers, please let me know your thoughts on that.
There was a problem hiding this comment.
Yeah that sounds correct 👍
|
This PR needs to be rebased |
c6fc4e4 to
ca7200c
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
kevingosse
left a comment
There was a problem hiding this comment.
LGTM + a few minor changes. Great work!
| var splitterBytes = FindSplitterPositions(sqlChars); | ||
| var outputLength = sqlChars.Length; | ||
| var end = outputLength; | ||
| var start = end > 0 ? PreviousSetBit(splitterBytes, end - 1) : -1; |
There was a problem hiding this comment.
NIT: end == 0 means that the string is empty. We should probably check this at the very beginning of the method and exit early when that happens
There was a problem hiding this comment.
Earlier I added the logic to not even call the method if the string was empty on the ObfuscatorTraceProcessor.cs file instead: https://github.com/DataDog/dd-trace-dotnet/pull/2498/files#diff-35a4636a4a751afda8b9ae23ab84ccc0f42591704b2ed3d70a6289ad902e42a3R50-R53
Is that good or should I moved it to the method instead as we expect most strings to not be empty anyways?
There was a problem hiding this comment.
Indeed, it gives the same result. Checking this here seem better as you don't ask the caller to do it.
Co-authored-by: Kevin Gosse <[email protected]>
Co-authored-by: Kevin Gosse <[email protected]>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| var splitterBytes = FindSplitterPositions(sqlChars); | ||
| var outputLength = sqlChars.Length; | ||
| var end = outputLength; | ||
| var start = end > 0 ? PreviousSetBit(splitterBytes, end - 1) : -1; |
There was a problem hiding this comment.
Indeed, it gives the same result. Checking this here seem better as you don't ask the caller to do it.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pierotibou
left a comment
There was a problem hiding this comment.
A very small nit.
Besides that, looks good. Great job! And thanks a lot
| if (span.Type == "sql" || span.Type == "cassandra") | ||
| { | ||
| span.ResourceName = ObfuscateSqlResource(span.ResourceName); | ||
| span.ResourceName = SqlObfuscator(span.ResourceName); |
There was a problem hiding this comment.
nitpicking again - (and clearly don't mind me)
But as it's not a class, but a function I'd change that to a verb. So ObfuscateSql or ObfuscateSqlResource would be better IMO
There was a problem hiding this comment.
TIL, yeah that does make, sense, made the new commits, will wait for the checks to run so I can merge.
Change function name from `SqlObfuscator` to `ObfuscateSqlResource`.
Code Coverage Report 📊✔️ Merging #2498 into master will not change line coverage
View the full report for further details: Datadog.Trace Breakdown ✔️
The following classes have significant coverage changes.
The following classes were added in #2498:
View the full reports for further details: |
Benchmarks Report 🐌Benchmarks for #2498 compared to master:
The following thresholds were used for comparing the benchmark speeds:
Allocation changes below 0.5% are ignored. Benchmark detailsBenchmarks.Trace.AgentWriterBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AppSecBodyBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AspNetCoreBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.DbCommandBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ElasticsearchBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.GraphQLBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.HttpClientBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ILoggerBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.Log4netBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.NLogBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.RedisBenchmark - Slower
|
| Benchmark | diff/base | Base Median (ns) | Diff Median (ns) | Modality |
|---|---|---|---|---|
| Benchmarks.Trace.RedisBenchmark.SendReceive‑netcoreapp3.1 | 1.181 | 2,083.64 | 2,460.30 |
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | SendReceive |
net472 | 2.45μs | 13.8ns | 91.7ns | 0.16 | 0 | 0 | 1.01 KB |
| master | SendReceive |
netcoreapp3.1 | 2.09μs | 8.68ns | 31.3ns | 0.0138 | 0 | 0 | 1.01 KB |
| #2498 | SendReceive |
net472 | 2.34μs | 10.8ns | 40.3ns | 0.16 | 0.00115 | 0 | 1.01 KB |
| #2498 | SendReceive |
netcoreapp3.1 | 2.48μs | 13.9ns | 96.5ns | 0.0137 | 0 | 0 | 1.01 KB |
Benchmarks.Trace.SerilogBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 5.9μs | 31.3ns | 159ns | 0.292 | 0 | 0 | 1.87 KB |
| master | EnrichedLog |
netcoreapp3.1 | 5.17μs | 16.6ns | 64.1ns | 0.0204 | 0 | 0 | 1.49 KB |
| #2498 | EnrichedLog |
net472 | 5.98μs | 27ns | 105ns | 0.29 | 0 | 0 | 1.87 KB |
| #2498 | EnrichedLog |
netcoreapp3.1 | 5.21μs | 20.4ns | 78.9ns | 0.0205 | 0 | 0 | 1.49 KB |
Benchmarks.Trace.SpanBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | StartFinishSpan |
net472 | 898ns | 3.86ns | 14.4ns | 0.0717 | 0 | 0 | 457 B |
| master | StartFinishSpan |
netcoreapp3.1 | 889ns | 3.93ns | 14.2ns | 0.00647 | 0 | 0 | 456 B |
| master | StartFinishScope |
net472 | 1.12μs | 4.23ns | 15.8ns | 0.0846 | 0 | 0 | 538 B |
| master | StartFinishScope |
netcoreapp3.1 | 1.05μs | 5.91ns | 46.6ns | 0.00802 | 0 | 0 | 576 B |
| #2498 | StartFinishSpan |
net472 | 917ns | 4.96ns | 28.5ns | 0.0718 | 0 | 0 | 457 B |
| #2498 | StartFinishSpan |
netcoreapp3.1 | 930ns | 5.25ns | 36ns | 0.00621 | 0 | 0 | 456 B |
| #2498 | StartFinishScope |
net472 | 1.26μs | 6.89ns | 40.2ns | 0.0843 | 0 | 0 | 538 B |
| #2498 | StartFinishScope |
netcoreapp3.1 | 1.05μs | 3ns | 11.6ns | 0.00793 | 0 | 0 | 576 B |
Benchmarks.Trace.TraceAnnotationsBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | RunOnMethodBegin |
net472 | 1.27μs | 6.67ns | 35.3ns | 0.0845 | 0 | 0 | 538 B |
| master | RunOnMethodBegin |
netcoreapp3.1 | 1.19μs | 6.27ns | 31.4ns | 0.00775 | 0 | 0 | 576 B |
| #2498 | RunOnMethodBegin |
net472 | 1.3μs | 7.09ns | 45.4ns | 0.0842 | 0 | 0 | 538 B |
| #2498 | RunOnMethodBegin |
netcoreapp3.1 | 1.13μs | 3.94ns | 15.3ns | 0.00803 | 0 | 0 | 576 B |
Summary of changes
This is a mock PR Based from Tony's CI App branch, the new changes are the implementation of an Obfuscator for SQL queries based on what the Java tracer currently supports.
Reason for change
To help reduce overhead on the work the Trace Agent does.
Implementation details
Still need to add the obfuscation processor and in the meantime added the method to obfuscate queries and related queries tests.