Skip to content

Commit 4e0d3db

Browse files
authored
Merge pull request #259 from diffix/edon/instrumentation
Add logs and instrumentation utils to query context
2 parents c278f2b + 5a872af commit 4e0d3db

File tree

7 files changed

+251
-120
lines changed

7 files changed

+251
-120
lines changed

src/OpenDiffix.Core.Tests/Anonymizer.Tests.fs

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,16 @@ let companyColumn = ColumnReference(2, StringType)
2626
let allAidColumns = ListExpr [ aidColumn; companyColumn ]
2727

2828
let executionContext =
29-
{ ExecutionContext.Default with
30-
QueryContext =
31-
{ QueryContext.Default with
32-
AnonymizationParams =
33-
{
34-
TableSettings = Map.empty
35-
Salt = [||]
36-
Suppression = { LowThreshold = 2; LowMeanGap = 0.0; SD = 0. }
37-
OutlierCount = { Lower = 1; Upper = 1 }
38-
TopCount = { Lower = 1; Upper = 1 }
39-
NoiseSD = 0.
40-
}
41-
}
42-
}
29+
(QueryContext.makeWithAnonParams
30+
{
31+
TableSettings = Map.empty
32+
Salt = [||]
33+
Suppression = { LowThreshold = 2; LowMeanGap = 0.0; SD = 0. }
34+
OutlierCount = { Lower = 1; Upper = 1 }
35+
TopCount = { Lower = 1; Upper = 1 }
36+
NoiseSD = 0.
37+
})
38+
|> ExecutionContext.fromQueryContext
4339

4440
let anonymizedAggregationContext =
4541
let threshold = { Lower = 2; Upper = 2 }
@@ -50,9 +46,7 @@ let anonymizedAggregationContext =
5046
TopCount = threshold
5147
}
5248

53-
{ ExecutionContext.Default with
54-
QueryContext = { QueryContext.Default with AnonymizationParams = anonParams }
55-
}
49+
QueryContext.makeWithAnonParams anonParams |> ExecutionContext.fromQueryContext
5650

5751
let evaluateAggregator fn args =
5852
evaluateAggregator executionContext fn args

src/OpenDiffix.Core.Tests/Executor.Tests.fs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ type Tests(db: DBFixture) =
2727
FunctionExpr(AggregateFunction(Count, { Distinct = true; OrderBy = [] }), [ expression ])
2828

2929
let executionContext =
30-
{ ExecutionContext.Default with
31-
QueryContext = { QueryContext.Default with DataProvider = db.DataProvider }
32-
}
30+
QueryContext.makeWithDataProvider db.DataProvider
31+
|> ExecutionContext.fromQueryContext
3332

3433
let execute plan =
3534
plan |> Executor.execute executionContext |> Seq.toList

src/OpenDiffix.Core.Tests/Expression.Tests.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ let colRef2 = ColumnReference(2, RealType)
535535
let evaluate expr = Expression.evaluate testRow expr
536536

537537
let evaluateAggregator fn args =
538-
evaluateAggregator ExecutionContext.Default fn args testRows
538+
evaluateAggregator (ExecutionContext.makeDefault ()) fn args testRows
539539

540540
[<Fact>]
541541
let ``evaluate scalar expressions`` () =

src/OpenDiffix.Core/CommonTypes.fs

Lines changed: 220 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module rec OpenDiffix.Core.CommonTypes
44

55
open System
6+
open System.Diagnostics
7+
open System.Runtime.CompilerServices
68

79
// ----------------------------------------------------------------
810
// Values
@@ -165,19 +167,14 @@ type QueryContext =
165167
{
166168
AnonymizationParams: AnonymizationParams
167169
DataProvider: IDataProvider
170+
Metadata: QueryMetadata
168171
}
169-
static member Default =
170-
{
171-
AnonymizationParams = AnonymizationParams.Default
172-
DataProvider =
173-
{ new IDataProvider with
174-
member _.OpenTable(_table, _columnIndices) = failwith "No tables in data provider"
175-
member _.GetSchema() = []
176-
member _.Dispose() = ()
177-
}
178-
}
179172

180-
type NoiseLayers = { BucketSeed: Hash }
173+
type NoiseLayers =
174+
{
175+
BucketSeed: Hash
176+
}
177+
static member Default = { BucketSeed = 0UL }
181178

182179
type ExecutionContext =
183180
{
@@ -186,4 +183,215 @@ type ExecutionContext =
186183
}
187184
member this.AnonymizationParams = this.QueryContext.AnonymizationParams
188185
member this.DataProvider = this.QueryContext.DataProvider
189-
static member Default = { QueryContext = QueryContext.Default; NoiseLayers = { BucketSeed = 0UL } }
186+
member this.Metadata = this.QueryContext.Metadata
187+
188+
// ----------------------------------------------------------------
189+
// Constants
190+
// ----------------------------------------------------------------
191+
192+
let NULL_TYPE = UnknownType "null_type"
193+
let MISSING_TYPE = UnknownType "missing_type"
194+
let MIXED_TYPE = UnknownType "mixed_type"
195+
196+
// ----------------------------------------------------------------
197+
// Functions
198+
// ----------------------------------------------------------------
199+
200+
module Row =
201+
let equalityComparer = LanguagePrimitives.FastGenericEqualityComparer<Row>
202+
203+
module ExpressionType =
204+
/// Resolves the common type from a list of types.
205+
let commonType types =
206+
types
207+
|> List.distinct
208+
|> function
209+
| [] -> MISSING_TYPE
210+
| [ t ] -> t
211+
| _ -> MIXED_TYPE
212+
213+
module Function =
214+
let fromString name =
215+
match name with
216+
| "count" -> AggregateFunction(Count, AggregateOptions.Default)
217+
| "sum" -> AggregateFunction(Sum, AggregateOptions.Default)
218+
| "diffix_count" -> AggregateFunction(DiffixCount, AggregateOptions.Default)
219+
| "diffix_low_count" -> AggregateFunction(DiffixLowCount, AggregateOptions.Default)
220+
| "+" -> ScalarFunction Add
221+
| "-" -> ScalarFunction Subtract
222+
| "*" -> ScalarFunction Multiply
223+
| "/" -> ScalarFunction Divide
224+
| "%" -> ScalarFunction Modulo
225+
| "round" -> ScalarFunction Round
226+
| "ceil" -> ScalarFunction Ceil
227+
| "floor" -> ScalarFunction Floor
228+
| "round_by" -> ScalarFunction RoundBy
229+
| "ceil_by" -> ScalarFunction CeilBy
230+
| "floor_by" -> ScalarFunction FloorBy
231+
| "abs" -> ScalarFunction Abs
232+
| "length" -> ScalarFunction Length
233+
| "lower" -> ScalarFunction Lower
234+
| "upper" -> ScalarFunction Upper
235+
| "substring" -> ScalarFunction Substring
236+
| "||" -> ScalarFunction Concat
237+
| "width_bucket" -> ScalarFunction WidthBucket
238+
| "cast" -> ScalarFunction Cast
239+
| other -> failwith $"Unknown function `{other}`"
240+
241+
module Table =
242+
/// Finds a column along with its index. The index is zero-based.
243+
let tryFindColumn table columnName =
244+
table.Columns
245+
|> List.indexed
246+
|> List.tryFind (fun (_index, column) -> String.equalsI column.Name columnName)
247+
248+
/// Finds a column along with its index. The index is zero-based. Fails if column not found.
249+
let findColumn table columnName =
250+
columnName
251+
|> tryFindColumn table
252+
|> function
253+
| Some column -> column
254+
| None -> failwith $"Could not find column `{columnName}` in table `{table.Name}`"
255+
256+
module Schema =
257+
/// Finds a table by name in the schema.
258+
let tryFindTable schema tableName =
259+
schema |> List.tryFind (fun table -> String.equalsI table.Name tableName)
260+
261+
/// Finds a table by name in the schema. Fails if table not found.
262+
let findTable schema tableName =
263+
tableName
264+
|> tryFindTable schema
265+
|> function
266+
| Some table -> table
267+
| None -> failwith $"Could not find table `{tableName}`."
268+
269+
module AnonymizationParams =
270+
/// Returns whether the given column in the table is an AID column.
271+
let isAidColumn anonParams tableName columnName =
272+
anonParams.TableSettings
273+
|> Map.tryFind tableName
274+
|> function
275+
| Some tableSettings -> tableSettings.AidColumns |> List.exists (String.equalsI columnName)
276+
| None -> false
277+
278+
module QueryContext =
279+
let private defaultDataProvider =
280+
{ new IDataProvider with
281+
member _.OpenTable(_table, _columnIndices) = failwith "No tables in data provider"
282+
member _.GetSchema() = []
283+
member _.Dispose() = ()
284+
}
285+
286+
let make anonParams dataProvider =
287+
{
288+
AnonymizationParams = anonParams
289+
DataProvider = dataProvider
290+
Metadata = QueryMetadata(fun _msg -> ())
291+
}
292+
293+
let makeDefault () =
294+
make AnonymizationParams.Default defaultDataProvider
295+
296+
let makeWithAnonParams anonParams = make anonParams defaultDataProvider
297+
298+
let makeWithDataProvider dataProvider =
299+
make AnonymizationParams.Default dataProvider
300+
301+
let withLogger logger queryContext =
302+
{ queryContext with Metadata = QueryMetadata(logger) }
303+
304+
module ExecutionContext =
305+
let fromQueryContext queryContext =
306+
{ QueryContext = queryContext; NoiseLayers = NoiseLayers.Default }
307+
308+
let makeDefault () =
309+
fromQueryContext (QueryContext.makeDefault ())
310+
311+
// ----------------------------------------------------------------
312+
// Logging & Instrumentation
313+
// ----------------------------------------------------------------
314+
315+
// Events are relative to init time, expressed in ticks (unit of 100ns).
316+
type Ticks = int64
317+
318+
type LogLevel =
319+
| DebugLevel
320+
| InfoLevel
321+
| WarningLevel
322+
323+
type LogMessage = { Timestamp: Ticks; Level: LogLevel; Message: string }
324+
325+
module Ticks =
326+
let private ticksPerMillisecond = float TimeSpan.TicksPerMillisecond
327+
328+
let toTimestamp (t: Ticks) = TimeSpan.FromTicks(t).ToString("c")
329+
330+
let toDuration (t: Ticks) =
331+
let ms = (float t / ticksPerMillisecond)
332+
333+
if ms >= 1000.0 then
334+
(ms / 1000.0).ToString("N3") + "s"
335+
else
336+
ms.ToString("N3") + "ms"
337+
338+
module LogMessage =
339+
let private levelToString =
340+
function
341+
| DebugLevel -> "[DBG]"
342+
| InfoLevel -> "[INF]"
343+
| WarningLevel -> "[WRN]"
344+
345+
let toString (message: LogMessage) : string =
346+
$"{Ticks.toTimestamp message.Timestamp} {levelToString message.Level} {message.Message}"
347+
348+
type LoggerCallback = LogMessage -> unit
349+
350+
type QueryMetadata(logger: LoggerCallback) =
351+
let globalTimer = Stopwatch.StartNew()
352+
let measurements = Collections.Generic.Dictionary<string, Ticks>()
353+
let counters = Collections.Generic.Dictionary<string, int>()
354+
355+
let makeMessage level message =
356+
{ Timestamp = globalTimer.Elapsed.Ticks; Level = level; Message = message }
357+
358+
[<Conditional("DEBUG")>]
359+
member this.LogDebug(message: string) : unit = logger (makeMessage DebugLevel message)
360+
361+
member this.Log(message: string) : unit = logger (makeMessage InfoLevel message)
362+
363+
member this.LogWarning(message: string) : unit =
364+
logger (makeMessage WarningLevel message)
365+
366+
member this.MeasureScope([<CallerMemberName>] ?event: string) : IDisposable =
367+
let event = event.Value
368+
let stopwatch = Stopwatch.StartNew()
369+
370+
{ new IDisposable with
371+
member _.Dispose() =
372+
let total = stopwatch.Elapsed.Ticks + (Dictionary.getOrDefault event 0L measurements)
373+
stopwatch.Reset()
374+
measurements.[event] <- total
375+
}
376+
377+
[<Conditional("DEBUG")>]
378+
member this.CountDebug([<CallerMemberName>] ?event: string) : unit = this.Count(event.Value)
379+
380+
member this.Count([<CallerMemberName>] ?event: string) : unit =
381+
let event = event.Value
382+
let currentCount = Dictionary.getOrDefault event 0 counters
383+
counters.[event] <- currentCount + 1
384+
385+
override this.ToString() =
386+
let builder = Text.StringBuilder()
387+
388+
if measurements.Count > 0 || counters.Count > 0 then
389+
builder.AppendLine("<Metadata>") |> ignore
390+
391+
measurements
392+
|> Seq.iter (fun pair -> builder.AppendLine($"{pair.Key}: {Ticks.toDuration pair.Value}") |> ignore)
393+
394+
counters
395+
|> Seq.iter (fun pair -> builder.AppendLine($"{pair.Key}: {pair.Value}") |> ignore)
396+
397+
builder.ToString()

src/OpenDiffix.Core/OpenDiffix.Core.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<TargetFramework>net5.0</TargetFramework>
44
</PropertyGroup>
55
<ItemGroup>
6-
<Compile Include="CommonTypes.fs" />
76
<Compile Include="Utils.fs" />
7+
<Compile Include="CommonTypes.fs" />
88
<Compile Include="Value.fs" />
99
<Compile Include="Expression.fs" />
1010
<Compile Include="Anonymizer.fs" />

src/OpenDiffix.Core/QueryEngine.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ let rec private extractColumns query =
66
query.TargetList
77
|> List.filter TargetEntry.isRegular
88
|> List.map (fun column -> //
9-
{ Name = column.Alias; Type = Expression.typeOf (column.Expression) }
9+
{ Name = column.Alias; Type = Expression.typeOf column.Expression }
1010
)
1111

1212
// ----------------------------------------------------------------
@@ -16,6 +16,8 @@ let rec private extractColumns query =
1616
type QueryResult = { Columns: Column list; Rows: Row list }
1717

1818
let run queryContext statement : QueryResult =
19+
use _measurer = queryContext.Metadata.MeasureScope()
20+
1921
let query, executionContext =
2022
statement
2123
|> Parser.parse

0 commit comments

Comments
 (0)