From b78c1a03c33aa17707656d1603147265e5b8a969 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:07:56 -0800 Subject: [PATCH 01/17] Add GroceryExample --- examples/GroceryExample/.vscode/launch.json | 30 ++++++ examples/GroceryExample/.vscode/tasks.json | 42 +++++++++ examples/GroceryExample/Extra.cs | 91 +++++++++++++++++++ examples/GroceryExample/GroceryExample.csproj | 17 ++++ examples/GroceryExample/GroceryStore.cs | 76 ++++++++++++++++ examples/GroceryExample/Program.cs | 50 ++++++++++ 6 files changed, 306 insertions(+) create mode 100644 examples/GroceryExample/.vscode/launch.json create mode 100644 examples/GroceryExample/.vscode/tasks.json create mode 100644 examples/GroceryExample/Extra.cs create mode 100644 examples/GroceryExample/GroceryExample.csproj create mode 100644 examples/GroceryExample/GroceryStore.cs create mode 100644 examples/GroceryExample/Program.cs diff --git a/examples/GroceryExample/.vscode/launch.json b/examples/GroceryExample/.vscode/launch.json new file mode 100644 index 0000000000..cdc0a4611d --- /dev/null +++ b/examples/GroceryExample/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "OS-COMMENT1": "Use IntelliSense to find out which attributes exist for C# debugging", + "OS-COMMENT2": "Use hover for the description of the existing attributes", + "OS-COMMENT3": "For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md", + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "OS-COMMENT4": "If you have changed target frameworks, make sure to update the program path.", + "program": "${workspaceFolder}/bin/Debug/net5.0/GroceryExample.dll", + "args": [], + "cwd": "${workspaceFolder}", + "OS-COMMENT5": "For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/examples/GroceryExample/.vscode/tasks.json b/examples/GroceryExample/.vscode/tasks.json new file mode 100644 index 0000000000..8dd9ed4fca --- /dev/null +++ b/examples/GroceryExample/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/GroceryExample.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/GroceryExample.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/GroceryExample.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/examples/GroceryExample/Extra.cs b/examples/GroceryExample/Extra.cs new file mode 100644 index 0000000000..8760ef3c1a --- /dev/null +++ b/examples/GroceryExample/Extra.cs @@ -0,0 +1,91 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class MyLabelSet : LabelSet + { + public MyLabelSet(params KeyValuePair[] labels) + { + List> list = new List>(); + foreach (var kv in labels) + { + list.Add(kv); + } + + Labels = list; + } + + public override IEnumerable> Labels { get; set; } = System.Linq.Enumerable.Empty>(); + } + + public class MyMetricProcessor : MetricProcessor + { + private List items = new List(); + + public override void FinishCollectionCycle(out IEnumerable metrics) + { + metrics = Interlocked.Exchange(ref items, new List()); + } + + public override void Process(Metric metric) + { + items.Add(metric); + } + } + + public class MyMetricExporter: MetricExporter + { + public override Task ExportAsync(IEnumerable metrics, CancellationToken cancellationToken) + { + return Task.Run(() => + { + StringBuilder sb = new StringBuilder(); + + foreach (var m in metrics) + { + sb.AppendLine($"[{m.MetricNamespace}:{m.MetricName}]"); + + foreach (var data in m.Data) + { + sb.Append(" Labels: "); + foreach (var l in data.Labels) + { + sb.Append($"{l.Key}={l.Value}, "); + } + + sb.AppendLine(); + } + } + + Console.WriteLine(sb.ToString()); + + return ExportResult.Success; + }); + } + } + +} \ No newline at end of file diff --git a/examples/GroceryExample/GroceryExample.csproj b/examples/GroceryExample/GroceryExample.csproj new file mode 100644 index 0000000000..c2c7583a4f --- /dev/null +++ b/examples/GroceryExample/GroceryExample.csproj @@ -0,0 +1,17 @@ + + + + Exe + net5.0 + + + + {99f8a331-05e9-45a5-89ba-4c54e825e5b2} + OpenTelemetry.Api + + + {ae3e3df5-4083-4c6e-a840-8271b0acde7e} + OpenTelemetry + + + \ No newline at end of file diff --git a/examples/GroceryExample/GroceryStore.cs b/examples/GroceryExample/GroceryStore.cs new file mode 100644 index 0000000000..a009a65a89 --- /dev/null +++ b/examples/GroceryExample/GroceryStore.cs @@ -0,0 +1,76 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class GroceryStore + { + private static Dictionary priceList = new Dictionary() + { + { "potato", 1.10 }, + { "tomato", 3.00 }, + }; + + private string storeName; + + private CounterMetric itemCounter; + private CounterMetric cashCounter; + + public GroceryStore(string storeName) + { + this.storeName = storeName; + + // Setup Metrics + + Meter meter = MeterProvider.Default.GetMeter("GroceryStore", "1.0.0"); + + itemCounter = meter.CreateInt64Counter("item_counter"); + + cashCounter = meter.CreateDoubleCounter("cash_counter"); + } + + public void ProcessOrder(string customer, params (string name, int qty)[] items) + { + double total_price = 0; + + foreach (var item in items) + { + total_price += item.qty * priceList[item.name]; + + // Record Metric + + var labels = new MyLabelSet( + new KeyValuePair("Customer", customer), + new KeyValuePair("Item", item.name)); + + itemCounter.Add(default(SpanContext), item.qty, labels); + } + + // Record Metric + + var labels2 = new MyLabelSet( + new KeyValuePair("Customer", customer)); + + cashCounter.Add(default(SpanContext), total_price, labels2); + } + } +} diff --git a/examples/GroceryExample/Program.cs b/examples/GroceryExample/Program.cs new file mode 100644 index 0000000000..ed2c4f50f1 --- /dev/null +++ b/examples/GroceryExample/Program.cs @@ -0,0 +1,50 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Threading.Tasks; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class Program + { + public static void Main(string[] args) + { + // Create Metric Pipeline + + var sdk = OpenTelemetry.Sdk.CreateMeterProviderBuilder() + .SetPushInterval(TimeSpan.FromMilliseconds(1000)) + .SetProcessor(new MyMetricProcessor()) + .SetExporter(new MyMetricExporter()) + .Build() + ; + + var store = new GroceryStore("Portland"); + store.ProcessOrder("customerA", ("potato", 2), ("tomato", 3)); + store.ProcessOrder("customerB", ("tomato", 10)); + store.ProcessOrder("customerC", ("potato", 2)); + store.ProcessOrder("customerA", ("tomato", 1)); + + // Wait for stuff to run + Task.Delay(5000).Wait(); + + // Shutdown Metric Pipeline + // pipeline.Stop(); + } + } +} From 0893e147310f2065c50d8719941a54715b8cf0b1 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:32:01 -0800 Subject: [PATCH 02/17] add readme.md --- examples/GroceryExample/GroceryStore.cs | 20 ++++++++++++++++---- examples/GroceryExample/readme.md | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 examples/GroceryExample/readme.md diff --git a/examples/GroceryExample/GroceryStore.cs b/examples/GroceryExample/GroceryStore.cs index a009a65a89..e7867f2a70 100644 --- a/examples/GroceryExample/GroceryStore.cs +++ b/examples/GroceryExample/GroceryStore.cs @@ -33,8 +33,11 @@ public class GroceryStore private string storeName; private CounterMetric itemCounter; + private CounterMetric cashCounter; + private BoundCounterMetric boundCashCounter; + public GroceryStore(string storeName) { this.storeName = storeName; @@ -46,31 +49,40 @@ public GroceryStore(string storeName) itemCounter = meter.CreateInt64Counter("item_counter"); cashCounter = meter.CreateDoubleCounter("cash_counter"); + + var labels = new MyLabelSet( + new KeyValuePair("Store", "Portland")); + + boundCashCounter = cashCounter.Bind(labels); } public void ProcessOrder(string customer, params (string name, int qty)[] items) { - double total_price = 0; + double totalPrice = 0; foreach (var item in items) { - total_price += item.qty * priceList[item.name]; + totalPrice += item.qty * priceList[item.name]; // Record Metric var labels = new MyLabelSet( + new KeyValuePair("Store", "Portland"), new KeyValuePair("Customer", customer), new KeyValuePair("Item", item.name)); - + itemCounter.Add(default(SpanContext), item.qty, labels); } // Record Metric var labels2 = new MyLabelSet( + new KeyValuePair("Store", "Portland"), new KeyValuePair("Customer", customer)); - cashCounter.Add(default(SpanContext), total_price, labels2); + cashCounter.Add(default(SpanContext), totalPrice, labels2); + + boundCashCounter.Add(default(SpanContext), totalPrice); } } } diff --git a/examples/GroceryExample/readme.md b/examples/GroceryExample/readme.md new file mode 100644 index 0000000000..bc3f164f0d --- /dev/null +++ b/examples/GroceryExample/readme.md @@ -0,0 +1,19 @@ +# Overview + +The goal of this Grocery example is to try and insturment the code. From this excersize we hope to discover and learn addtional topics for discussions. + +We are focus only on the API side at the moment. It is known that SDK implementation will likely affect how the API is designed, but we will make our best judgement to tolerate the situation at this time. + +# Topics for discussions + +- Need some kind of concrete LabelSet() in API. Access to LabelSetSdk() is unavailable from API side. + +- It is inconvienent to have to pass a default(SpanContext) when we don't care about spans. Need additional prototypes to make SpanContext optional. + +- We create instrument with CreateInt64Counter() but it returns a generic Counter. Seems like it should return a Int64Counter instead. + +- I don't believe it's possible to new MeterProvider(). Thus, the only way to access is via the Default property. We can probably simplify MeterProvider.Default.GetMeter() to simply MeterProvider.GetMeter(). + +- Bound counters does not allow adding more labels. Ideally, we would bind Store, but still allow passing in additional lables (i.e. Customer) when recording measurements. + +- Need shutdown() for SDK From faf5b769124fc4c7dbf5c2fa6ac4cfe3bace1347 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:48:31 -0800 Subject: [PATCH 03/17] remove .vscode folder --- examples/GroceryExample/.vscode/launch.json | 30 --------------- examples/GroceryExample/.vscode/tasks.json | 42 --------------------- 2 files changed, 72 deletions(-) delete mode 100644 examples/GroceryExample/.vscode/launch.json delete mode 100644 examples/GroceryExample/.vscode/tasks.json diff --git a/examples/GroceryExample/.vscode/launch.json b/examples/GroceryExample/.vscode/launch.json deleted file mode 100644 index cdc0a4611d..0000000000 --- a/examples/GroceryExample/.vscode/launch.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "OS-COMMENT1": "Use IntelliSense to find out which attributes exist for C# debugging", - "OS-COMMENT2": "Use hover for the description of the existing attributes", - "OS-COMMENT3": "For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md", - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "OS-COMMENT4": "If you have changed target frameworks, make sure to update the program path.", - "program": "${workspaceFolder}/bin/Debug/net5.0/GroceryExample.dll", - "args": [], - "cwd": "${workspaceFolder}", - "OS-COMMENT5": "For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console", - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} \ No newline at end of file diff --git a/examples/GroceryExample/.vscode/tasks.json b/examples/GroceryExample/.vscode/tasks.json deleted file mode 100644 index 8dd9ed4fca..0000000000 --- a/examples/GroceryExample/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/GroceryExample.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/GroceryExample.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/GroceryExample.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file From 6c129cf0eb54af34f5f72038dd5c267bd8e3d1d6 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Thu, 11 Feb 2021 15:52:07 -0800 Subject: [PATCH 04/17] Clean up style/formatting --- examples/GroceryExample/.vscode/launch.json | 30 +++++++++++++ examples/GroceryExample/.vscode/tasks.json | 42 +++++++++++++++++++ examples/GroceryExample/GroceryStore.cs | 18 ++++---- .../{Extra.cs => Misc/MyMetricExporter.cs} | 37 +--------------- .../GroceryExample/Misc/MyMetricProcessor.cs | 39 +++++++++++++++++ examples/GroceryExample/MyLabelSet.cs | 39 +++++++++++++++++ examples/GroceryExample/Program.cs | 5 ++- examples/GroceryExample/readme.md | 25 +++++++---- 8 files changed, 181 insertions(+), 54 deletions(-) create mode 100644 examples/GroceryExample/.vscode/launch.json create mode 100644 examples/GroceryExample/.vscode/tasks.json rename examples/GroceryExample/{Extra.cs => Misc/MyMetricExporter.cs} (59%) create mode 100644 examples/GroceryExample/Misc/MyMetricProcessor.cs create mode 100644 examples/GroceryExample/MyLabelSet.cs diff --git a/examples/GroceryExample/.vscode/launch.json b/examples/GroceryExample/.vscode/launch.json new file mode 100644 index 0000000000..cdc0a4611d --- /dev/null +++ b/examples/GroceryExample/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "OS-COMMENT1": "Use IntelliSense to find out which attributes exist for C# debugging", + "OS-COMMENT2": "Use hover for the description of the existing attributes", + "OS-COMMENT3": "For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md", + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "OS-COMMENT4": "If you have changed target frameworks, make sure to update the program path.", + "program": "${workspaceFolder}/bin/Debug/net5.0/GroceryExample.dll", + "args": [], + "cwd": "${workspaceFolder}", + "OS-COMMENT5": "For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/examples/GroceryExample/.vscode/tasks.json b/examples/GroceryExample/.vscode/tasks.json new file mode 100644 index 0000000000..8dd9ed4fca --- /dev/null +++ b/examples/GroceryExample/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/GroceryExample.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/GroceryExample.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/GroceryExample.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/examples/GroceryExample/GroceryStore.cs b/examples/GroceryExample/GroceryStore.cs index e7867f2a70..494c785684 100644 --- a/examples/GroceryExample/GroceryStore.cs +++ b/examples/GroceryExample/GroceryStore.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ public class GroceryStore private string storeName; private CounterMetric itemCounter; - + private CounterMetric cashCounter; private BoundCounterMetric boundCashCounter; @@ -46,14 +46,14 @@ public GroceryStore(string storeName) Meter meter = MeterProvider.Default.GetMeter("GroceryStore", "1.0.0"); - itemCounter = meter.CreateInt64Counter("item_counter"); + this.itemCounter = meter.CreateInt64Counter("item_counter"); - cashCounter = meter.CreateDoubleCounter("cash_counter"); + this.cashCounter = meter.CreateDoubleCounter("cash_counter"); var labels = new MyLabelSet( new KeyValuePair("Store", "Portland")); - boundCashCounter = cashCounter.Bind(labels); + this.boundCashCounter = this.cashCounter.Bind(labels); } public void ProcessOrder(string customer, params (string name, int qty)[] items) @@ -71,7 +71,7 @@ public void ProcessOrder(string customer, params (string name, int qty)[] items) new KeyValuePair("Customer", customer), new KeyValuePair("Item", item.name)); - itemCounter.Add(default(SpanContext), item.qty, labels); + this.itemCounter.Add(default(SpanContext), item.qty, labels); } // Record Metric @@ -80,9 +80,9 @@ public void ProcessOrder(string customer, params (string name, int qty)[] items) new KeyValuePair("Store", "Portland"), new KeyValuePair("Customer", customer)); - cashCounter.Add(default(SpanContext), totalPrice, labels2); + this.cashCounter.Add(default(SpanContext), totalPrice, labels2); - boundCashCounter.Add(default(SpanContext), totalPrice); + this.boundCashCounter.Add(default(SpanContext), totalPrice); } } -} +} \ No newline at end of file diff --git a/examples/GroceryExample/Extra.cs b/examples/GroceryExample/Misc/MyMetricExporter.cs similarity index 59% rename from examples/GroceryExample/Extra.cs rename to examples/GroceryExample/Misc/MyMetricExporter.cs index 8760ef3c1a..1616af5049 100644 --- a/examples/GroceryExample/Extra.cs +++ b/examples/GroceryExample/Misc/MyMetricExporter.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,45 +19,13 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using OpenTelemetry.Metrics; using OpenTelemetry.Metrics.Export; #pragma warning disable CS0618 namespace GroceryExample { - public class MyLabelSet : LabelSet - { - public MyLabelSet(params KeyValuePair[] labels) - { - List> list = new List>(); - foreach (var kv in labels) - { - list.Add(kv); - } - - Labels = list; - } - - public override IEnumerable> Labels { get; set; } = System.Linq.Enumerable.Empty>(); - } - - public class MyMetricProcessor : MetricProcessor - { - private List items = new List(); - - public override void FinishCollectionCycle(out IEnumerable metrics) - { - metrics = Interlocked.Exchange(ref items, new List()); - } - - public override void Process(Metric metric) - { - items.Add(metric); - } - } - - public class MyMetricExporter: MetricExporter + public class MyMetricExporter : MetricExporter { public override Task ExportAsync(IEnumerable metrics, CancellationToken cancellationToken) { @@ -87,5 +55,4 @@ public override Task ExportAsync(IEnumerable metrics, Canc }); } } - } \ No newline at end of file diff --git a/examples/GroceryExample/Misc/MyMetricProcessor.cs b/examples/GroceryExample/Misc/MyMetricProcessor.cs new file mode 100644 index 0000000000..e1ec21dc5f --- /dev/null +++ b/examples/GroceryExample/Misc/MyMetricProcessor.cs @@ -0,0 +1,39 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.Threading; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class MyMetricProcessor : MetricProcessor + { + private List items = new List(); + + public override void FinishCollectionCycle(out IEnumerable metrics) + { + metrics = Interlocked.Exchange(ref this.items, new List()); + } + + public override void Process(Metric metric) + { + this.items.Add(metric); + } + } +} \ No newline at end of file diff --git a/examples/GroceryExample/MyLabelSet.cs b/examples/GroceryExample/MyLabelSet.cs new file mode 100644 index 0000000000..7f240fa240 --- /dev/null +++ b/examples/GroceryExample/MyLabelSet.cs @@ -0,0 +1,39 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using OpenTelemetry.Metrics; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class MyLabelSet : LabelSet + { + public MyLabelSet(params KeyValuePair[] labels) + { + List> list = new List>(); + foreach (var kv in labels) + { + list.Add(kv); + } + + this.Labels = list; + } + + public override IEnumerable> Labels { get; set; } = System.Linq.Enumerable.Empty>(); + } +} \ No newline at end of file diff --git a/examples/GroceryExample/Program.cs b/examples/GroceryExample/Program.cs index ed2c4f50f1..5583e3ee6c 100644 --- a/examples/GroceryExample/Program.cs +++ b/examples/GroceryExample/Program.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,7 @@ public static void Main(string[] args) ; var store = new GroceryStore("Portland"); + store.ProcessOrder("customerA", ("potato", 2), ("tomato", 3)); store.ProcessOrder("customerB", ("tomato", 10)); store.ProcessOrder("customerC", ("potato", 2)); @@ -44,7 +45,7 @@ public static void Main(string[] args) Task.Delay(5000).Wait(); // Shutdown Metric Pipeline - // pipeline.Stop(); + // sdk.Shutdown(); } } } diff --git a/examples/GroceryExample/readme.md b/examples/GroceryExample/readme.md index bc3f164f0d..3c42432722 100644 --- a/examples/GroceryExample/readme.md +++ b/examples/GroceryExample/readme.md @@ -1,19 +1,28 @@ # Overview -The goal of this Grocery example is to try and insturment the code. From this excersize we hope to discover and learn addtional topics for discussions. +The goal of this Grocery example is to try and instrument the code. From this +excersize we hope to discover and learn additional topics for discussions. -We are focus only on the API side at the moment. It is known that SDK implementation will likely affect how the API is designed, but we will make our best judgement to tolerate the situation at this time. +We are focus only on the API side at the moment. It is known that SDK implementation +will likely affect how the API is designed, but we will make our best judgement to +tolerate the situation at this time. -# Topics for discussions +## Topics for discussions -- Need some kind of concrete LabelSet() in API. Access to LabelSetSdk() is unavailable from API side. +- Need some kind of concrete LabelSet() in API. Access to LabelSetSdk() is unavailable + from API side. -- It is inconvienent to have to pass a default(SpanContext) when we don't care about spans. Need additional prototypes to make SpanContext optional. +- It is inconvenient to have to pass a default(SpanContext) when we don't care about spans. + Need additional prototypes to make SpanContext optional. -- We create instrument with CreateInt64Counter() but it returns a generic Counter. Seems like it should return a Int64Counter instead. +- We create instrument with CreateInt64Counter() but it returns a generic Counter. + Seems like it should return a Int64Counter instead. -- I don't believe it's possible to new MeterProvider(). Thus, the only way to access is via the Default property. We can probably simplify MeterProvider.Default.GetMeter() to simply MeterProvider.GetMeter(). +- It's not allowed to new MeterProvider(). Thus, the only way to access is via the Default property. + We can probably simplify MeterProvider.Default.GetMeter() + to simply MeterProvider.GetMeter(). -- Bound counters does not allow adding more labels. Ideally, we would bind Store, but still allow passing in additional lables (i.e. Customer) when recording measurements. +- Bound counters does not allow adding more labels. Ideally, we would bind Store, + but still allow passing in additional lables (i.e. Customer) when recording measurements. - Need shutdown() for SDK From 2137a58a3c9dd866091752430defd8201203194e Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Thu, 11 Feb 2021 16:05:42 -0800 Subject: [PATCH 05/17] Fix style/formatting --- examples/GroceryExample/GroceryStore.cs | 2 +- .../GroceryExample/Misc/MyMetricExporter.cs | 2 +- .../GroceryExample/Misc/MyMetricProcessor.cs | 2 +- examples/GroceryExample/MyLabelSet.cs | 2 +- examples/GroceryExample/Program.cs | 2 +- examples/GroceryExample/readme.md | 29 ++++++++++--------- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/examples/GroceryExample/GroceryStore.cs b/examples/GroceryExample/GroceryStore.cs index 494c785684..d3df65ce32 100644 --- a/examples/GroceryExample/GroceryStore.cs +++ b/examples/GroceryExample/GroceryStore.cs @@ -85,4 +85,4 @@ public void ProcessOrder(string customer, params (string name, int qty)[] items) this.boundCashCounter.Add(default(SpanContext), totalPrice); } } -} \ No newline at end of file +} diff --git a/examples/GroceryExample/Misc/MyMetricExporter.cs b/examples/GroceryExample/Misc/MyMetricExporter.cs index 1616af5049..e0fdb05d54 100644 --- a/examples/GroceryExample/Misc/MyMetricExporter.cs +++ b/examples/GroceryExample/Misc/MyMetricExporter.cs @@ -55,4 +55,4 @@ public override Task ExportAsync(IEnumerable metrics, Canc }); } } -} \ No newline at end of file +} diff --git a/examples/GroceryExample/Misc/MyMetricProcessor.cs b/examples/GroceryExample/Misc/MyMetricProcessor.cs index e1ec21dc5f..5fe5ae8bbb 100644 --- a/examples/GroceryExample/Misc/MyMetricProcessor.cs +++ b/examples/GroceryExample/Misc/MyMetricProcessor.cs @@ -36,4 +36,4 @@ public override void Process(Metric metric) this.items.Add(metric); } } -} \ No newline at end of file +} diff --git a/examples/GroceryExample/MyLabelSet.cs b/examples/GroceryExample/MyLabelSet.cs index 7f240fa240..130212949c 100644 --- a/examples/GroceryExample/MyLabelSet.cs +++ b/examples/GroceryExample/MyLabelSet.cs @@ -36,4 +36,4 @@ public MyLabelSet(params KeyValuePair[] labels) public override IEnumerable> Labels { get; set; } = System.Linq.Enumerable.Empty>(); } -} \ No newline at end of file +} diff --git a/examples/GroceryExample/Program.cs b/examples/GroceryExample/Program.cs index 5583e3ee6c..ea63236759 100644 --- a/examples/GroceryExample/Program.cs +++ b/examples/GroceryExample/Program.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/GroceryExample/readme.md b/examples/GroceryExample/readme.md index 3c42432722..e4d0d5d339 100644 --- a/examples/GroceryExample/readme.md +++ b/examples/GroceryExample/readme.md @@ -3,26 +3,27 @@ The goal of this Grocery example is to try and instrument the code. From this excersize we hope to discover and learn additional topics for discussions. -We are focus only on the API side at the moment. It is known that SDK implementation -will likely affect how the API is designed, but we will make our best judgement to -tolerate the situation at this time. +We are focus only on the API side at the moment. It is known that SDK +implementation will likely affect how the API is designed, but we will make our +best judgement to tolerate the situation at this time. ## Topics for discussions -- Need some kind of concrete LabelSet() in API. Access to LabelSetSdk() is unavailable - from API side. +- Need some kind of concrete LabelSet() in API. Access to LabelSetSdk() is +unavailable from API side. -- It is inconvenient to have to pass a default(SpanContext) when we don't care about spans. - Need additional prototypes to make SpanContext optional. +- It is inconvenient to have to pass a default(SpanContext) when we don't care +about spans. Need additional prototypes to make SpanContext optional. -- We create instrument with CreateInt64Counter() but it returns a generic Counter. - Seems like it should return a Int64Counter instead. +- We create instrument with CreateInt64Counter() but it returns a generic +Counter. Seems like it should return a Int64Counter instead. -- It's not allowed to new MeterProvider(). Thus, the only way to access is via the Default property. - We can probably simplify MeterProvider.Default.GetMeter() - to simply MeterProvider.GetMeter(). +- It's not allowed to new MeterProvider(). Thus, the only way to access is via +the Default property. We can probably simplify MeterProvider.Default.GetMeter() +to simply MeterProvider.GetMeter(). -- Bound counters does not allow adding more labels. Ideally, we would bind Store, - but still allow passing in additional lables (i.e. Customer) when recording measurements. +- Bound counters does not allow adding more labels. Ideally, we would bind +Store, but still allow passing in additional lables (i.e. Customer) when +recording measurements. - Need shutdown() for SDK From 530ad85afd365a90e42cc531b23bb34edf1ebb72 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Fri, 12 Feb 2021 12:18:36 -0800 Subject: [PATCH 06/17] Basic Web Server framework --- examples/HttpServerExample/Client.cs | 44 +++++++++ .../HttpServerExample.csproj | 17 ++++ examples/HttpServerExample/Program.cs | 56 +++++++++++ examples/HttpServerExample/WebServer.cs | 95 +++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 examples/HttpServerExample/Client.cs create mode 100644 examples/HttpServerExample/HttpServerExample.csproj create mode 100644 examples/HttpServerExample/Program.cs create mode 100644 examples/HttpServerExample/WebServer.cs diff --git a/examples/HttpServerExample/Client.cs b/examples/HttpServerExample/Client.cs new file mode 100644 index 0000000000..9bdcbc6f69 --- /dev/null +++ b/examples/HttpServerExample/Client.cs @@ -0,0 +1,44 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace HttpServerExample +{ + public class Client + { + private HttpClient client = new HttpClient(); + + public async Task SendRequest(string name) + { + try + { + HttpResponseMessage response = await this.client.GetAsync($"http://127.0.0.1:3000/{name}"); + + response.EnsureSuccessStatusCode(); + + string responseBody = await response.Content.ReadAsStringAsync(); + Console.WriteLine(responseBody); + } + catch (HttpRequestException e) + { + Console.WriteLine("Exception: Message :{0} ", e.Message); + } + } + } +} diff --git a/examples/HttpServerExample/HttpServerExample.csproj b/examples/HttpServerExample/HttpServerExample.csproj new file mode 100644 index 0000000000..25698210c8 --- /dev/null +++ b/examples/HttpServerExample/HttpServerExample.csproj @@ -0,0 +1,17 @@ + + + + Exe + net5.0 + + + + {99f8a331-05e9-45a5-89ba-4c54e825e5b2} + OpenTelemetry.Api + + + {ae3e3df5-4083-4c6e-a840-8271b0acde7e} + OpenTelemetry + + + diff --git a/examples/HttpServerExample/Program.cs b/examples/HttpServerExample/Program.cs new file mode 100644 index 0000000000..8c86343c80 --- /dev/null +++ b/examples/HttpServerExample/Program.cs @@ -0,0 +1,56 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; + +namespace HttpServerExample +{ + public class Program + { + public static void Main(string[] args) + { + var webserver = new WebServer(); + + webserver.Start(); + + var client = new Client(); + + Console.WriteLine("Type \"send {name}\" to send. Type \"exit\" to exit."); + while (true) + { + var cmdLine = Console.ReadLine(); + if (cmdLine != null) + { + var cmds = cmdLine.Split(" "); + + if (cmds[0] == "exit") + { + break; + } + else if (cmds[0] =="send") + { + var names = cmds.AsSpan().Slice(1); + var name = String.Join("/", names.ToArray()); + + var t = client.SendRequest(name); + } + } + } + + webserver.Stop(); + } + } +} diff --git a/examples/HttpServerExample/WebServer.cs b/examples/HttpServerExample/WebServer.cs new file mode 100644 index 0000000000..ae949f9081 --- /dev/null +++ b/examples/HttpServerExample/WebServer.cs @@ -0,0 +1,95 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace HttpServerExample +{ + public class WebServer + { + private CancellationTokenSource tokenSrc = new CancellationTokenSource(); + + private Task serverTask; + + private HttpListener listener = new HttpListener(); + + public WebServer() + { + this.listener.Prefixes.Add("http://127.0.0.1:3000/"); + } + + public void Start() + { + var token = this.tokenSrc.Token; + + this.serverTask = Task.Run(async () => + { + Console.WriteLine("Server Started."); + + this.listener.Start(); + + while (!token.IsCancellationRequested) + { + var contextTask = this.listener.GetContextAsync(); + + Task.WaitAny(new Task[] { contextTask }, token); + if (contextTask.IsCompletedSuccessfully) + { + var context = await contextTask; + HttpListenerRequest request = context.Request; + + // Parse request + + var url = request.Url; + string responseString = $"Hello world! {url.AbsolutePath}"; + byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString); + + // Format output + + HttpListenerResponse response = context.Response; + response.ContentLength64 = buffer.Length; + response.StatusCode = 200; + + System.IO.Stream output = response.OutputStream; + output.Write(buffer, 0, buffer.Length); + output.Close(); + } + } + + this.listener.Stop(); + + Console.WriteLine("Server Stopped."); + }); + } + + public void Stop() + { + this.tokenSrc.Cancel(); + + try + { + this.serverTask.Wait(); + } + catch (Exception) + { + // Do Nothing + } + } + } +} From 3ddaee22c93410d868b38641e165bd2688b58011 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Fri, 12 Feb 2021 15:55:45 -0800 Subject: [PATCH 07/17] Basic Web Server and Web Client. --- examples/HttpServerExample/Client.cs | 49 ++++++++++++++----- examples/HttpServerExample/Program.cs | 38 +++++---------- examples/HttpServerExample/WebServer.cs | 64 +++++++++++++------------ 3 files changed, 82 insertions(+), 69 deletions(-) diff --git a/examples/HttpServerExample/Client.cs b/examples/HttpServerExample/Client.cs index 9bdcbc6f69..595ba1ed88 100644 --- a/examples/HttpServerExample/Client.cs +++ b/examples/HttpServerExample/Client.cs @@ -16,29 +16,52 @@ using System; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; namespace HttpServerExample { public class Client { - private HttpClient client = new HttpClient(); - - public async Task SendRequest(string name) + public static Task StartClientTask(string url, int periodMilli, CancellationToken token) { - try + Task task = Task.Run(async () => { - HttpResponseMessage response = await this.client.GetAsync($"http://127.0.0.1:3000/{name}"); + Console.WriteLine("Client Started."); - response.EnsureSuccessStatusCode(); + while (!token.IsCancellationRequested) + { + try + { + HttpClient client = new HttpClient(); - string responseBody = await response.Content.ReadAsStringAsync(); - Console.WriteLine(responseBody); - } - catch (HttpRequestException e) - { - Console.WriteLine("Exception: Message :{0} ", e.Message); - } + HttpResponseMessage response = await client.GetAsync(url); + + response.EnsureSuccessStatusCode(); + + string responseBody = await response.Content.ReadAsStringAsync(); + + Console.WriteLine($"Client got \"{responseBody}\""); + } + catch (HttpRequestException e) + { + Console.WriteLine("Client Exception: {0}", e.Message); + } + + try + { + await Task.Delay(periodMilli, token); + } + catch (Exception) + { + // Do Nothing + } + } + + Console.WriteLine("Client Stopped."); + }); + + return task; } } } diff --git a/examples/HttpServerExample/Program.cs b/examples/HttpServerExample/Program.cs index 8c86343c80..a05151f9b5 100644 --- a/examples/HttpServerExample/Program.cs +++ b/examples/HttpServerExample/Program.cs @@ -15,42 +15,30 @@ // using System; +using System.Threading; +using System.Threading.Tasks; namespace HttpServerExample { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - var webserver = new WebServer(); - - webserver.Start(); + CancellationTokenSource cancelSrc = new CancellationTokenSource(); - var client = new Client(); + var client1Task = Client.StartClientTask("http://127.0.0.1:3000/Test1", 1000, cancelSrc.Token); + var client2Task = Client.StartClientTask("http://127.0.0.1:3000/Test2", 5000, cancelSrc.Token); - Console.WriteLine("Type \"send {name}\" to send. Type \"exit\" to exit."); - while (true) - { - var cmdLine = Console.ReadLine(); - if (cmdLine != null) - { - var cmds = cmdLine.Split(" "); - if (cmds[0] == "exit") - { - break; - } - else if (cmds[0] =="send") - { - var names = cmds.AsSpan().Slice(1); - var name = String.Join("/", names.ToArray()); + var webserver = new WebServer(); + var webserverTask = webserver.StartServerTask("http://127.0.0.1:3000/", cancelSrc.Token); - var t = client.SendRequest(name); - } - } - } + Console.WriteLine("Press [ENTER] to exit."); + var cmdline = Console.ReadLine(); + cancelSrc.Cancel(); - webserver.Stop(); + webserver.Shutdown(); + await webserverTask; } } } diff --git a/examples/HttpServerExample/WebServer.cs b/examples/HttpServerExample/WebServer.cs index ae949f9081..37a54ba353 100644 --- a/examples/HttpServerExample/WebServer.cs +++ b/examples/HttpServerExample/WebServer.cs @@ -23,32 +23,41 @@ namespace HttpServerExample { public class WebServer { - private CancellationTokenSource tokenSrc = new CancellationTokenSource(); - - private Task serverTask; - - private HttpListener listener = new HttpListener(); - public WebServer() { - this.listener.Prefixes.Add("http://127.0.0.1:3000/"); + // Initialize Web Server } - public void Start() + public void Shutdown() { - var token = this.tokenSrc.Token; + // Shutdown + } - this.serverTask = Task.Run(async () => - { + public Task StartServerTask(string prefix, CancellationToken token) + { + HttpListener listener = new HttpListener(); + + listener.Prefixes.Add(prefix); + + Task serverTask = Task.Run(async () => + { Console.WriteLine("Server Started."); - this.listener.Start(); + listener.Start(); while (!token.IsCancellationRequested) { - var contextTask = this.listener.GetContextAsync(); + var contextTask = listener.GetContextAsync(); + + try + { + Task.WaitAny(new Task[] { contextTask }, token); + } + catch (Exception) + { + // Do Nothing + } - Task.WaitAny(new Task[] { contextTask }, token); if (contextTask.IsCompletedSuccessfully) { var context = await contextTask; @@ -56,40 +65,33 @@ public void Start() // Parse request - var url = request.Url; - string responseString = $"Hello world! {url.AbsolutePath}"; - byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString); + var path = request.Url.AbsolutePath; + + Console.WriteLine($"Server request for {path}"); // Format output + string responseString = $"Hello world for {path}"; + byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString); + HttpListenerResponse response = context.Response; response.ContentLength64 = buffer.Length; response.StatusCode = 200; + // Return Response + System.IO.Stream output = response.OutputStream; output.Write(buffer, 0, buffer.Length); output.Close(); } } - this.listener.Stop(); + listener.Stop(); Console.WriteLine("Server Stopped."); }); - } - - public void Stop() - { - this.tokenSrc.Cancel(); - try - { - this.serverTask.Wait(); - } - catch (Exception) - { - // Do Nothing - } + return serverTask; } } } From 5fc46f81fd216804078b02b8d8d02cbf19e83789 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Tue, 16 Feb 2021 12:57:08 -0800 Subject: [PATCH 08/17] Instrument my example --- examples/HttpServerExample/MyLabelSet.cs | 41 ++++++++++ examples/HttpServerExample/MyLibrary.cs | 96 ++++++++++++++++++++++++ examples/HttpServerExample/Program.cs | 67 +++++++++++++++++ examples/HttpServerExample/WebServer.cs | 68 ++++++++++++++++- examples/HttpServerExample/readme.md | 77 +++++++++++++++++++ 5 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 examples/HttpServerExample/MyLabelSet.cs create mode 100644 examples/HttpServerExample/MyLibrary.cs create mode 100644 examples/HttpServerExample/readme.md diff --git a/examples/HttpServerExample/MyLabelSet.cs b/examples/HttpServerExample/MyLabelSet.cs new file mode 100644 index 0000000000..1dc3a82fa8 --- /dev/null +++ b/examples/HttpServerExample/MyLabelSet.cs @@ -0,0 +1,41 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class MyLabelSet : LabelSet + { + public MyLabelSet(params (string name, string value)[] labels) + { + List> list = new List>(); + foreach (var label in labels) + { + list.Add(KeyValuePair.Create(label.name, label.value)); + } + + Labels = list; + } + } +} diff --git a/examples/HttpServerExample/MyLibrary.cs b/examples/HttpServerExample/MyLibrary.cs new file mode 100644 index 0000000000..81d2ed4e21 --- /dev/null +++ b/examples/HttpServerExample/MyLibrary.cs @@ -0,0 +1,96 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using OpenTelemetry.Metrics; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class MyLibrary + { + Random rand = new Random(); + + public MyLibrary() + { + Meter meter = MeterProvider.Default.GetMeter("MyLibrary", "1.0.0"); + + var hostLabels = new MyLabelSet(("Host Name", GetHostName())); + + meter.CreateDoubleObserver("ServerRoomTemp", (k) => + { + double temp = GetServerRoomTemperature(); + k.Observe(temp, hostLabels); + }, + true); + + meter.CreateDoubleObserver("SystemCpuUsage", (k) => + { + int cpu = GetSystemCpu(); + k.Observe(cpu, hostLabels); + }, + true); + + meter.CreateDoubleObserver("ProcessCpuUsage", (k) => + { + int cpu = GetSystemCpu(); + int pid = GetProcessCpu(); + + var processLabels = new MyLabelSet( + ("Host Name", GetHostName()), + ("Process Id", $"{GetProcessId()}") + ); + k.Observe(cpu, processLabels); + }, + true); + } + + public void Shutdown() + { + // Shutdown + } + + public string GetHostName() + { + return "MachineA"; + } + + public int GetProcessId() + { + var r = rand.Next(10); + return 1230 + r; + } + + public double GetServerRoomTemperature() + { + var r = rand.Next(10); + return 70.2 + r; + } + + public int GetSystemCpu() + { + var r = rand.Next(20); + return 30 + r; + } + + public int GetProcessCpu() + { + var r = rand.Next(20); + return 10 + r; + } + } +} diff --git a/examples/HttpServerExample/Program.cs b/examples/HttpServerExample/Program.cs index a05151f9b5..98be6e3617 100644 --- a/examples/HttpServerExample/Program.cs +++ b/examples/HttpServerExample/Program.cs @@ -15,8 +15,13 @@ // using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using OpenTelemetry.Metrics; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 namespace HttpServerExample { @@ -29,6 +34,25 @@ public static async Task Main(string[] args) var client1Task = Client.StartClientTask("http://127.0.0.1:3000/Test1", 1000, cancelSrc.Token); var client2Task = Client.StartClientTask("http://127.0.0.1:3000/Test2", 5000, cancelSrc.Token); + // My Application + + var pipeFiveSeconds = OpenTelemetry.Sdk.CreateMeterProviderBuilder() + .SetPushInterval(TimeSpan.FromSeconds(5)) + .Build(); + + var pipeOneMinute = OpenTelemetry.Sdk.CreateMeterProviderBuilder() + .SetPushInterval(TimeSpan.FromSeconds(1)) + + // No way to specify which metrics I'm interested in! + .SetProcessor(new MyFilterProcessor("ServerRoomTemp")) + + .Build(); + + // How do I configure the Default provider? + // How do I tie Library to multiple pipeline? + MeterProvider.SetDefault(pipeOneMinute); + + // ======== var webserver = new WebServer(); var webserverTask = webserver.StartServerTask("http://127.0.0.1:3000/", cancelSrc.Token); @@ -39,6 +63,49 @@ public static async Task Main(string[] args) webserver.Shutdown(); await webserverTask; + + // ======== + + // There is no Shutdown() + // pipeFiveSeconds.Shutdown(); + // pipeOneMinute.Shutdown(); + } + + public class MyFilterProcessor : MetricProcessor + { + private static readonly object lockList = new object(); + private static List list = new List(); + + private string[] filters; + + public MyFilterProcessor(params string[] filters) + { + this.filters = filters; + } + + public override void FinishCollectionCycle(out IEnumerable metrics) + { + var oldList = Interlocked.Exchange(ref list, new List()); + metrics = oldList; + } + + public override void Process(Metric metric) + { + Console.WriteLine($"Processing {metric.MetricName}, count={metric.Data.Count}..."); + + foreach (var filter in this.filters) + { + if (metric.MetricName.Contains(filter)) + { + lock (lockList) + { + list.Add(metric); + } + + break; + } + } + } } } } diff --git a/examples/HttpServerExample/WebServer.cs b/examples/HttpServerExample/WebServer.cs index 37a54ba353..9d9edca95a 100644 --- a/examples/HttpServerExample/WebServer.cs +++ b/examples/HttpServerExample/WebServer.cs @@ -18,14 +18,39 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +#pragma warning disable CS0618 namespace HttpServerExample { public class WebServer { + MyLibrary library; + + MeasureMetric duration; + CounterMetric errorCount; + CounterMetric incomingCount; + CounterMetric outgoingCount; + public WebServer() { // Initialize Web Server + + library = new MyLibrary(); + + Meter meter = MeterProvider.Default.GetMeter("MyServer", "1.0.0"); + + duration = meter.CreateInt64Measure("Server.Duration", true); + // How to tell it what unit the measurements are in? + + errorCount = meter.CreateInt64Counter("Server.Errors", true); + + incomingCount = meter.CreateInt64Counter("Server.Request.Incoming", true); + + outgoingCount = meter.CreateInt64Counter("Server.Request.Outgoing", true); } public void Shutdown() @@ -45,6 +70,8 @@ public Task StartServerTask(string prefix, CancellationToken token) listener.Start(); + Stopwatch sw = new Stopwatch(); + while (!token.IsCancellationRequested) { var contextTask = listener.GetContextAsync(); @@ -58,11 +85,23 @@ public Task StartServerTask(string prefix, CancellationToken token) // Do Nothing } - if (contextTask.IsCompletedSuccessfully) + if (contextTask.IsCompletedSuccessfully && !contextTask.IsFaulted) { + sw.Reset(); + var context = await contextTask; HttpListenerRequest request = context.Request; + var requestLabels = new MyLabelSet( + ("Host Name", library.GetHostName()), + ("Process Id", library.GetProcessId().ToString()), + ("Method", request.HttpMethod), + ("Peer IP", request.RemoteEndPoint.Address.ToString()), + ("Port", request.Url.Port.ToString()) + ); + + incomingCount.Add(default(SpanContext), 1, requestLabels); + // Parse request var path = request.Url.AbsolutePath; @@ -83,6 +122,33 @@ public Task StartServerTask(string prefix, CancellationToken token) System.IO.Stream output = response.OutputStream; output.Write(buffer, 0, buffer.Length); output.Close(); + + // ======== + + outgoingCount.Add(default(SpanContext), 1, requestLabels); + + var elapsed = sw.ElapsedMilliseconds; + var labels = new MyLabelSet( + ("Host Name", library.GetHostName()), + ("Process Id", library.GetProcessId().ToString()), + ("Method", request.HttpMethod), + ("Peer IP", request.RemoteEndPoint.Address.ToString()), + ("Port", request.Url.Port.ToString()), + + // Need to include Status Code + ("Status Code", response.StatusCode.ToString()) + ); + duration.Record(default(SpanContext), elapsed, labels); + } + else + { + // Count # of errors we have + + var labels = new MyLabelSet( + ("Host Name", library.GetHostName()), + ("Process Id", library.GetProcessId().ToString()) + ); + errorCount.Add(default(SpanContext), 1, labels); } } diff --git a/examples/HttpServerExample/readme.md b/examples/HttpServerExample/readme.md new file mode 100644 index 0000000000..2de5d4c636 --- /dev/null +++ b/examples/HttpServerExample/readme.md @@ -0,0 +1,77 @@ +# OpenTelemetry HTTP Server Demo + +## Scenario + +The _OpenTelemetry HTTP Server_ demo shows how a library developer X could use +metrics API to instrument a library, and how the application developer Y can +configure the library to use OpenTelemetry SDK in a final application. X and Y +are working for different companies and they don't communicate. The demo has two +parts - the library `io.opentelemetry.example.httpserver.instrumented` (owned by X) +and the server app `io.opentelemetry.example.httpserver.application` (owned by Y): + +* How developer X could instrument the library code in a vendor agnostic way + * Performance is critical for X + * X doesn't know which time series and which dimension will Y pick + * X doesn't know the aggregation time window, nor the final destination of the + metrics +* How developer Y could configure the SDK and exporter + * How should Y hook up the metrics SDK with the library + * How should Y configure the time window(s) and destination(s) + * How should Y pick the time series and the dimensions + +### Library Requirements + +The library developer (developer X) will expose the following metrics out of +box: + +#### Process CPU Usage + +| Host Name | Process ID | CPU% [0.0, 100.0] | +| --------- | ---------- | ----------------- | +| MachineA | 1234 | 15.3 | + +#### System CPU Usage + +| Host Name | CPU% [0, 100] | +| --------- | ------------- | +| MachineA | 30 | + +#### Server Room Temperature + +| Host Name | Temperature (F) | +| --------- | --------------- | +| MachineA | 65.3 | + +#### HTTP Server Duration + +| Host Name | Process ID | HTTP Method | HTTP Host | HTTP Status Code | HTTP Flavor | Peer IP | Peer Port | Host IP | Host Port | Duration (ms) | +| --------- | ---------- | ----------- | --------- | ---------------- | ----------- | --------- | --------- | --------- | --------- | ------------- | +| MachineA | 1234 | GET | otel.org | 200 | 1.1 | 127.0.0.1 | 51327 | 127.0.0.1 | 80 | 8.5 | +| MachineA | 1234 | POST | otel.org | 304 | 1.1 | 127.0.0.1 | 51328 | 127.0.0.1 | 80 | 100.0 | + +### Application Requirements + +The application owner (developer Y) would only want the following metrics: + +* [System CPU Usage](#system-cpu-usage) reported every 5 seconds +* [Server Room Temperature](#server-room-temperature) reported every minute +* [HTTP Server Duration](#http-server-duration), reported every 5 seconds, with + a subset of the dimensions: + * Host Name + * HTTP Method + * HTTP Host + * HTTP Status Code + * 90%, 95%, 99% and 99.9% latency +* HTTP request counters, reported every 5 seconds: + * Total number of received HTTP requests + * Total number of finished HTTP requests + * Number of ongoing HTTP requests (concurrent HTTP requests) + + | Host Name | Process ID | HTTP Host | Received Requests | Finished Requests | Concurrent Requests | + | --------- | ---------- | --------- | ----------------- | ----------------- | ------------------- | + | MachineA | 1234 | otel.org | 630 | 601 | 29 | + | MachineA | 5678 | otel.org | 1005 | 1001 | 4 | +* Exception samples (examplar) - in case HTTP 5xx happened, developer Y would + want to see a sample request with all the dimensions (IP, Port, etc.) + +## How to run From a10ae2e157e8c056d83558f448b5e237fcffa248 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:34:33 -0800 Subject: [PATCH 09/17] Fix dotnet-format issues --- examples/HttpServerExample/MyLabelSet.cs | 4 +-- examples/HttpServerExample/MyLibrary.cs | 23 ++++++------- examples/HttpServerExample/Program.cs | 2 +- examples/HttpServerExample/WebServer.cs | 43 ++++++++++++------------ 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/examples/HttpServerExample/MyLabelSet.cs b/examples/HttpServerExample/MyLabelSet.cs index 1dc3a82fa8..c06e45772a 100644 --- a/examples/HttpServerExample/MyLabelSet.cs +++ b/examples/HttpServerExample/MyLabelSet.cs @@ -29,13 +29,13 @@ public class MyLabelSet : LabelSet { public MyLabelSet(params (string name, string value)[] labels) { - List> list = new List>(); + List> list = new List>(); foreach (var label in labels) { list.Add(KeyValuePair.Create(label.name, label.value)); } - Labels = list; + this.Labels = list; } } } diff --git a/examples/HttpServerExample/MyLibrary.cs b/examples/HttpServerExample/MyLibrary.cs index 81d2ed4e21..39d5543135 100644 --- a/examples/HttpServerExample/MyLibrary.cs +++ b/examples/HttpServerExample/MyLibrary.cs @@ -31,32 +31,29 @@ public MyLibrary() var hostLabels = new MyLabelSet(("Host Name", GetHostName())); - meter.CreateDoubleObserver("ServerRoomTemp", (k) => + meter.CreateDoubleObserver("ServerRoomTemp", (k) => { - double temp = GetServerRoomTemperature(); + double temp = this.GetServerRoomTemperature(); k.Observe(temp, hostLabels); - }, - true); + }, true); - meter.CreateDoubleObserver("SystemCpuUsage", (k) => + meter.CreateDoubleObserver("SystemCpuUsage", (k) => { - int cpu = GetSystemCpu(); + int cpu = this.GetSystemCpu(); k.Observe(cpu, hostLabels); - }, - true); + }, true); - meter.CreateDoubleObserver("ProcessCpuUsage", (k) => + meter.CreateDoubleObserver("ProcessCpuUsage", (k) => { - int cpu = GetSystemCpu(); - int pid = GetProcessCpu(); + int cpu = this.GetSystemCpu(); + int pid = this.GetProcessCpu(); var processLabels = new MyLabelSet( ("Host Name", GetHostName()), ("Process Id", $"{GetProcessId()}") ); k.Observe(cpu, processLabels); - }, - true); + }, true); } public void Shutdown() diff --git a/examples/HttpServerExample/Program.cs b/examples/HttpServerExample/Program.cs index 98be6e3617..c43214346f 100644 --- a/examples/HttpServerExample/Program.cs +++ b/examples/HttpServerExample/Program.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/HttpServerExample/WebServer.cs b/examples/HttpServerExample/WebServer.cs index 9d9edca95a..1670cefca5 100644 --- a/examples/HttpServerExample/WebServer.cs +++ b/examples/HttpServerExample/WebServer.cs @@ -15,10 +15,10 @@ // using System; +using System.Diagnostics; using System.Net; using System.Threading; using System.Threading.Tasks; -using System.Diagnostics; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -28,29 +28,29 @@ namespace HttpServerExample { public class WebServer { - MyLibrary library; + private MyLibrary library; - MeasureMetric duration; - CounterMetric errorCount; - CounterMetric incomingCount; - CounterMetric outgoingCount; + private MeasureMetric duration; + private CounterMetric errorCount; + private CounterMetric incomingCount; + private CounterMetric outgoingCount; public WebServer() { // Initialize Web Server - library = new MyLibrary(); + this.library = new MyLibrary(); Meter meter = MeterProvider.Default.GetMeter("MyServer", "1.0.0"); - duration = meter.CreateInt64Measure("Server.Duration", true); + this.duration = meter.CreateInt64Measure("Server.Duration", true); // How to tell it what unit the measurements are in? - errorCount = meter.CreateInt64Counter("Server.Errors", true); + this.errorCount = meter.CreateInt64Counter("Server.Errors", true); - incomingCount = meter.CreateInt64Counter("Server.Request.Incoming", true); + this.incomingCount = meter.CreateInt64Counter("Server.Request.Incoming", true); - outgoingCount = meter.CreateInt64Counter("Server.Request.Outgoing", true); + this.outgoingCount = meter.CreateInt64Counter("Server.Request.Outgoing", true); } public void Shutdown() @@ -93,14 +93,14 @@ public Task StartServerTask(string prefix, CancellationToken token) HttpListenerRequest request = context.Request; var requestLabels = new MyLabelSet( - ("Host Name", library.GetHostName()), - ("Process Id", library.GetProcessId().ToString()), + ("Host Name", this.library.GetHostName()), + ("Process Id", this.library.GetProcessId().ToString()), ("Method", request.HttpMethod), ("Peer IP", request.RemoteEndPoint.Address.ToString()), ("Port", request.Url.Port.ToString()) ); - incomingCount.Add(default(SpanContext), 1, requestLabels); + this.incomingCount.Add(default(SpanContext), 1, requestLabels); // Parse request @@ -129,26 +129,25 @@ public Task StartServerTask(string prefix, CancellationToken token) var elapsed = sw.ElapsedMilliseconds; var labels = new MyLabelSet( - ("Host Name", library.GetHostName()), - ("Process Id", library.GetProcessId().ToString()), + ("Host Name", this.library.GetHostName()), + ("Process Id", this.library.GetProcessId().ToString()), ("Method", request.HttpMethod), ("Peer IP", request.RemoteEndPoint.Address.ToString()), ("Port", request.Url.Port.ToString()), - // Need to include Status Code ("Status Code", response.StatusCode.ToString()) ); - duration.Record(default(SpanContext), elapsed, labels); + this.duration.Record(default(SpanContext), elapsed, labels); } else { // Count # of errors we have - + var labels = new MyLabelSet( - ("Host Name", library.GetHostName()), - ("Process Id", library.GetProcessId().ToString()) + ("Host Name", this.library.GetHostName()), + ("Process Id", this.library.GetProcessId().ToString()) ); - errorCount.Add(default(SpanContext), 1, labels); + this.errorCount.Add(default(SpanContext), 1, labels); } } From a714b72d33497dd7350f61571e7d4ce0a9929d87 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:40:05 -0800 Subject: [PATCH 10/17] Fix MarkDownLint issues --- examples/HttpServerExample/.markdownlint.json | 7 +++++++ examples/HttpServerExample/readme.md | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 examples/HttpServerExample/.markdownlint.json diff --git a/examples/HttpServerExample/.markdownlint.json b/examples/HttpServerExample/.markdownlint.json new file mode 100644 index 0000000000..5a1bce80f6 --- /dev/null +++ b/examples/HttpServerExample/.markdownlint.json @@ -0,0 +1,7 @@ +{ + "default": true, + "line-length": { + "tables": false + } +} + \ No newline at end of file diff --git a/examples/HttpServerExample/readme.md b/examples/HttpServerExample/readme.md index 2de5d4c636..7b1df457f5 100644 --- a/examples/HttpServerExample/readme.md +++ b/examples/HttpServerExample/readme.md @@ -6,8 +6,9 @@ The _OpenTelemetry HTTP Server_ demo shows how a library developer X could use metrics API to instrument a library, and how the application developer Y can configure the library to use OpenTelemetry SDK in a final application. X and Y are working for different companies and they don't communicate. The demo has two -parts - the library `io.opentelemetry.example.httpserver.instrumented` (owned by X) -and the server app `io.opentelemetry.example.httpserver.application` (owned by Y): +parts - the library `io.opentelemetry.example.httpserver.instrumented` (owned by +X) and the server app `io.opentelemetry.example.httpserver.application` (owned +by Y): * How developer X could instrument the library code in a vendor agnostic way * Performance is critical for X From e4c6765fbdc197db5ac8d26e4334d6f29787b4fa Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:40:36 -0800 Subject: [PATCH 11/17] Fix markdownlint issues --- examples/GroceryExample/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/GroceryExample/readme.md b/examples/GroceryExample/readme.md index e4d0d5d339..23376044f2 100644 --- a/examples/GroceryExample/readme.md +++ b/examples/GroceryExample/readme.md @@ -16,7 +16,7 @@ unavailable from API side. about spans. Need additional prototypes to make SpanContext optional. - We create instrument with CreateInt64Counter() but it returns a generic -Counter. Seems like it should return a Int64Counter instead. +Counter<long>. Seems like it should return a Int64Counter instead. - It's not allowed to new MeterProvider(). Thus, the only way to access is via the Default property. We can probably simplify MeterProvider.Default.GetMeter() From 9b221160d0481968053b9a0b15847a02fdbe9abe Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:44:36 -0800 Subject: [PATCH 12/17] fix markdownlint issues --- .../HttpServerExample/.markdownlint.json => .markdownlint.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/HttpServerExample/.markdownlint.json => .markdownlint.json (100%) diff --git a/examples/HttpServerExample/.markdownlint.json b/.markdownlint.json similarity index 100% rename from examples/HttpServerExample/.markdownlint.json rename to .markdownlint.json From b84e2806f2200cd1442d5efd12d85ddfef862893 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:53:54 -0800 Subject: [PATCH 13/17] Fix EOL to LF only for sanitycheck.py --- examples/GroceryExample/GroceryExample.csproj | 32 +- examples/GroceryExample/GroceryStore.cs | 176 +++++----- .../GroceryExample/Misc/MyMetricExporter.cs | 116 +++---- .../GroceryExample/Misc/MyMetricProcessor.cs | 78 ++--- examples/GroceryExample/MyLabelSet.cs | 78 ++--- examples/GroceryExample/Program.cs | 102 +++--- examples/GroceryExample/readme.md | 58 ++-- examples/HttpServerExample/Client.cs | 134 ++++---- .../HttpServerExample.csproj | 34 +- examples/HttpServerExample/MyLabelSet.cs | 82 ++--- examples/HttpServerExample/MyLibrary.cs | 186 +++++----- examples/HttpServerExample/Program.cs | 222 ++++++------ examples/HttpServerExample/WebServer.cs | 324 +++++++++--------- examples/HttpServerExample/readme.md | 156 ++++----- 14 files changed, 889 insertions(+), 889 deletions(-) diff --git a/examples/GroceryExample/GroceryExample.csproj b/examples/GroceryExample/GroceryExample.csproj index c2c7583a4f..c992da82e1 100644 --- a/examples/GroceryExample/GroceryExample.csproj +++ b/examples/GroceryExample/GroceryExample.csproj @@ -1,17 +1,17 @@ - - - - Exe - net5.0 - - - - {99f8a331-05e9-45a5-89ba-4c54e825e5b2} - OpenTelemetry.Api - - - {ae3e3df5-4083-4c6e-a840-8271b0acde7e} - OpenTelemetry - - + + + + Exe + net5.0 + + + + {99f8a331-05e9-45a5-89ba-4c54e825e5b2} + OpenTelemetry.Api + + + {ae3e3df5-4083-4c6e-a840-8271b0acde7e} + OpenTelemetry + + \ No newline at end of file diff --git a/examples/GroceryExample/GroceryStore.cs b/examples/GroceryExample/GroceryStore.cs index d3df65ce32..fba33a1aae 100644 --- a/examples/GroceryExample/GroceryStore.cs +++ b/examples/GroceryExample/GroceryStore.cs @@ -1,88 +1,88 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Collections.Generic; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; - -#pragma warning disable CS0618 - -namespace GroceryExample -{ - public class GroceryStore - { - private static Dictionary priceList = new Dictionary() - { - { "potato", 1.10 }, - { "tomato", 3.00 }, - }; - - private string storeName; - - private CounterMetric itemCounter; - - private CounterMetric cashCounter; - - private BoundCounterMetric boundCashCounter; - - public GroceryStore(string storeName) - { - this.storeName = storeName; - - // Setup Metrics - - Meter meter = MeterProvider.Default.GetMeter("GroceryStore", "1.0.0"); - - this.itemCounter = meter.CreateInt64Counter("item_counter"); - - this.cashCounter = meter.CreateDoubleCounter("cash_counter"); - - var labels = new MyLabelSet( - new KeyValuePair("Store", "Portland")); - - this.boundCashCounter = this.cashCounter.Bind(labels); - } - - public void ProcessOrder(string customer, params (string name, int qty)[] items) - { - double totalPrice = 0; - - foreach (var item in items) - { - totalPrice += item.qty * priceList[item.name]; - - // Record Metric - - var labels = new MyLabelSet( - new KeyValuePair("Store", "Portland"), - new KeyValuePair("Customer", customer), - new KeyValuePair("Item", item.name)); - - this.itemCounter.Add(default(SpanContext), item.qty, labels); - } - - // Record Metric - - var labels2 = new MyLabelSet( - new KeyValuePair("Store", "Portland"), - new KeyValuePair("Customer", customer)); - - this.cashCounter.Add(default(SpanContext), totalPrice, labels2); - - this.boundCashCounter.Add(default(SpanContext), totalPrice); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class GroceryStore + { + private static Dictionary priceList = new Dictionary() + { + { "potato", 1.10 }, + { "tomato", 3.00 }, + }; + + private string storeName; + + private CounterMetric itemCounter; + + private CounterMetric cashCounter; + + private BoundCounterMetric boundCashCounter; + + public GroceryStore(string storeName) + { + this.storeName = storeName; + + // Setup Metrics + + Meter meter = MeterProvider.Default.GetMeter("GroceryStore", "1.0.0"); + + this.itemCounter = meter.CreateInt64Counter("item_counter"); + + this.cashCounter = meter.CreateDoubleCounter("cash_counter"); + + var labels = new MyLabelSet( + new KeyValuePair("Store", "Portland")); + + this.boundCashCounter = this.cashCounter.Bind(labels); + } + + public void ProcessOrder(string customer, params (string name, int qty)[] items) + { + double totalPrice = 0; + + foreach (var item in items) + { + totalPrice += item.qty * priceList[item.name]; + + // Record Metric + + var labels = new MyLabelSet( + new KeyValuePair("Store", "Portland"), + new KeyValuePair("Customer", customer), + new KeyValuePair("Item", item.name)); + + this.itemCounter.Add(default(SpanContext), item.qty, labels); + } + + // Record Metric + + var labels2 = new MyLabelSet( + new KeyValuePair("Store", "Portland"), + new KeyValuePair("Customer", customer)); + + this.cashCounter.Add(default(SpanContext), totalPrice, labels2); + + this.boundCashCounter.Add(default(SpanContext), totalPrice); + } + } +} diff --git a/examples/GroceryExample/Misc/MyMetricExporter.cs b/examples/GroceryExample/Misc/MyMetricExporter.cs index e0fdb05d54..c67fa5bc79 100644 --- a/examples/GroceryExample/Misc/MyMetricExporter.cs +++ b/examples/GroceryExample/Misc/MyMetricExporter.cs @@ -1,58 +1,58 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using OpenTelemetry.Metrics.Export; - -#pragma warning disable CS0618 - -namespace GroceryExample -{ - public class MyMetricExporter : MetricExporter - { - public override Task ExportAsync(IEnumerable metrics, CancellationToken cancellationToken) - { - return Task.Run(() => - { - StringBuilder sb = new StringBuilder(); - - foreach (var m in metrics) - { - sb.AppendLine($"[{m.MetricNamespace}:{m.MetricName}]"); - - foreach (var data in m.Data) - { - sb.Append(" Labels: "); - foreach (var l in data.Labels) - { - sb.Append($"{l.Key}={l.Value}, "); - } - - sb.AppendLine(); - } - } - - Console.WriteLine(sb.ToString()); - - return ExportResult.Success; - }); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class MyMetricExporter : MetricExporter + { + public override Task ExportAsync(IEnumerable metrics, CancellationToken cancellationToken) + { + return Task.Run(() => + { + StringBuilder sb = new StringBuilder(); + + foreach (var m in metrics) + { + sb.AppendLine($"[{m.MetricNamespace}:{m.MetricName}]"); + + foreach (var data in m.Data) + { + sb.Append(" Labels: "); + foreach (var l in data.Labels) + { + sb.Append($"{l.Key}={l.Value}, "); + } + + sb.AppendLine(); + } + } + + Console.WriteLine(sb.ToString()); + + return ExportResult.Success; + }); + } + } +} diff --git a/examples/GroceryExample/Misc/MyMetricProcessor.cs b/examples/GroceryExample/Misc/MyMetricProcessor.cs index 5fe5ae8bbb..e092c0348d 100644 --- a/examples/GroceryExample/Misc/MyMetricProcessor.cs +++ b/examples/GroceryExample/Misc/MyMetricProcessor.cs @@ -1,39 +1,39 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Collections.Generic; -using System.Threading; -using OpenTelemetry.Metrics.Export; - -#pragma warning disable CS0618 - -namespace GroceryExample -{ - public class MyMetricProcessor : MetricProcessor - { - private List items = new List(); - - public override void FinishCollectionCycle(out IEnumerable metrics) - { - metrics = Interlocked.Exchange(ref this.items, new List()); - } - - public override void Process(Metric metric) - { - this.items.Add(metric); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.Threading; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class MyMetricProcessor : MetricProcessor + { + private List items = new List(); + + public override void FinishCollectionCycle(out IEnumerable metrics) + { + metrics = Interlocked.Exchange(ref this.items, new List()); + } + + public override void Process(Metric metric) + { + this.items.Add(metric); + } + } +} diff --git a/examples/GroceryExample/MyLabelSet.cs b/examples/GroceryExample/MyLabelSet.cs index 130212949c..fe8ee4dfda 100644 --- a/examples/GroceryExample/MyLabelSet.cs +++ b/examples/GroceryExample/MyLabelSet.cs @@ -1,39 +1,39 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Collections.Generic; -using OpenTelemetry.Metrics; - -#pragma warning disable CS0618 - -namespace GroceryExample -{ - public class MyLabelSet : LabelSet - { - public MyLabelSet(params KeyValuePair[] labels) - { - List> list = new List>(); - foreach (var kv in labels) - { - list.Add(kv); - } - - this.Labels = list; - } - - public override IEnumerable> Labels { get; set; } = System.Linq.Enumerable.Empty>(); - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using OpenTelemetry.Metrics; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class MyLabelSet : LabelSet + { + public MyLabelSet(params KeyValuePair[] labels) + { + List> list = new List>(); + foreach (var kv in labels) + { + list.Add(kv); + } + + this.Labels = list; + } + + public override IEnumerable> Labels { get; set; } = System.Linq.Enumerable.Empty>(); + } +} diff --git a/examples/GroceryExample/Program.cs b/examples/GroceryExample/Program.cs index ea63236759..e6a1c8399e 100644 --- a/examples/GroceryExample/Program.cs +++ b/examples/GroceryExample/Program.cs @@ -1,51 +1,51 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Threading.Tasks; - -#pragma warning disable CS0618 - -namespace GroceryExample -{ - public class Program - { - public static void Main(string[] args) - { - // Create Metric Pipeline - - var sdk = OpenTelemetry.Sdk.CreateMeterProviderBuilder() - .SetPushInterval(TimeSpan.FromMilliseconds(1000)) - .SetProcessor(new MyMetricProcessor()) - .SetExporter(new MyMetricExporter()) - .Build() - ; - - var store = new GroceryStore("Portland"); - - store.ProcessOrder("customerA", ("potato", 2), ("tomato", 3)); - store.ProcessOrder("customerB", ("tomato", 10)); - store.ProcessOrder("customerC", ("potato", 2)); - store.ProcessOrder("customerA", ("tomato", 1)); - - // Wait for stuff to run - Task.Delay(5000).Wait(); - - // Shutdown Metric Pipeline - // sdk.Shutdown(); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Threading.Tasks; + +#pragma warning disable CS0618 + +namespace GroceryExample +{ + public class Program + { + public static void Main(string[] args) + { + // Create Metric Pipeline + + var sdk = OpenTelemetry.Sdk.CreateMeterProviderBuilder() + .SetPushInterval(TimeSpan.FromMilliseconds(1000)) + .SetProcessor(new MyMetricProcessor()) + .SetExporter(new MyMetricExporter()) + .Build() + ; + + var store = new GroceryStore("Portland"); + + store.ProcessOrder("customerA", ("potato", 2), ("tomato", 3)); + store.ProcessOrder("customerB", ("tomato", 10)); + store.ProcessOrder("customerC", ("potato", 2)); + store.ProcessOrder("customerA", ("tomato", 1)); + + // Wait for stuff to run + Task.Delay(5000).Wait(); + + // Shutdown Metric Pipeline + // sdk.Shutdown(); + } + } +} diff --git a/examples/GroceryExample/readme.md b/examples/GroceryExample/readme.md index 23376044f2..e76cedd50f 100644 --- a/examples/GroceryExample/readme.md +++ b/examples/GroceryExample/readme.md @@ -1,29 +1,29 @@ -# Overview - -The goal of this Grocery example is to try and instrument the code. From this -excersize we hope to discover and learn additional topics for discussions. - -We are focus only on the API side at the moment. It is known that SDK -implementation will likely affect how the API is designed, but we will make our -best judgement to tolerate the situation at this time. - -## Topics for discussions - -- Need some kind of concrete LabelSet() in API. Access to LabelSetSdk() is -unavailable from API side. - -- It is inconvenient to have to pass a default(SpanContext) when we don't care -about spans. Need additional prototypes to make SpanContext optional. - -- We create instrument with CreateInt64Counter() but it returns a generic -Counter<long>. Seems like it should return a Int64Counter instead. - -- It's not allowed to new MeterProvider(). Thus, the only way to access is via -the Default property. We can probably simplify MeterProvider.Default.GetMeter() -to simply MeterProvider.GetMeter(). - -- Bound counters does not allow adding more labels. Ideally, we would bind -Store, but still allow passing in additional lables (i.e. Customer) when -recording measurements. - -- Need shutdown() for SDK +# Overview + +The goal of this Grocery example is to try and instrument the code. From this +excersize we hope to discover and learn additional topics for discussions. + +We are focus only on the API side at the moment. It is known that SDK +implementation will likely affect how the API is designed, but we will make our +best judgement to tolerate the situation at this time. + +## Topics for discussions + +- Need some kind of concrete LabelSet() in API. Access to LabelSetSdk() is +unavailable from API side. + +- It is inconvenient to have to pass a default(SpanContext) when we don't care +about spans. Need additional prototypes to make SpanContext optional. + +- We create instrument with CreateInt64Counter() but it returns a generic +Counter<long>. Seems like it should return a Int64Counter instead. + +- It's not allowed to new MeterProvider(). Thus, the only way to access is via +the Default property. We can probably simplify MeterProvider.Default.GetMeter() +to simply MeterProvider.GetMeter(). + +- Bound counters does not allow adding more labels. Ideally, we would bind +Store, but still allow passing in additional lables (i.e. Customer) when +recording measurements. + +- Need shutdown() for SDK diff --git a/examples/HttpServerExample/Client.cs b/examples/HttpServerExample/Client.cs index 595ba1ed88..744f029a14 100644 --- a/examples/HttpServerExample/Client.cs +++ b/examples/HttpServerExample/Client.cs @@ -1,67 +1,67 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace HttpServerExample -{ - public class Client - { - public static Task StartClientTask(string url, int periodMilli, CancellationToken token) - { - Task task = Task.Run(async () => - { - Console.WriteLine("Client Started."); - - while (!token.IsCancellationRequested) - { - try - { - HttpClient client = new HttpClient(); - - HttpResponseMessage response = await client.GetAsync(url); - - response.EnsureSuccessStatusCode(); - - string responseBody = await response.Content.ReadAsStringAsync(); - - Console.WriteLine($"Client got \"{responseBody}\""); - } - catch (HttpRequestException e) - { - Console.WriteLine("Client Exception: {0}", e.Message); - } - - try - { - await Task.Delay(periodMilli, token); - } - catch (Exception) - { - // Do Nothing - } - } - - Console.WriteLine("Client Stopped."); - }); - - return task; - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace HttpServerExample +{ + public class Client + { + public static Task StartClientTask(string url, int periodMilli, CancellationToken token) + { + Task task = Task.Run(async () => + { + Console.WriteLine("Client Started."); + + while (!token.IsCancellationRequested) + { + try + { + HttpClient client = new HttpClient(); + + HttpResponseMessage response = await client.GetAsync(url); + + response.EnsureSuccessStatusCode(); + + string responseBody = await response.Content.ReadAsStringAsync(); + + Console.WriteLine($"Client got \"{responseBody}\""); + } + catch (HttpRequestException e) + { + Console.WriteLine("Client Exception: {0}", e.Message); + } + + try + { + await Task.Delay(periodMilli, token); + } + catch (Exception) + { + // Do Nothing + } + } + + Console.WriteLine("Client Stopped."); + }); + + return task; + } + } +} diff --git a/examples/HttpServerExample/HttpServerExample.csproj b/examples/HttpServerExample/HttpServerExample.csproj index 25698210c8..006dbfb420 100644 --- a/examples/HttpServerExample/HttpServerExample.csproj +++ b/examples/HttpServerExample/HttpServerExample.csproj @@ -1,17 +1,17 @@ - - - - Exe - net5.0 - - - - {99f8a331-05e9-45a5-89ba-4c54e825e5b2} - OpenTelemetry.Api - - - {ae3e3df5-4083-4c6e-a840-8271b0acde7e} - OpenTelemetry - - - + + + + Exe + net5.0 + + + + {99f8a331-05e9-45a5-89ba-4c54e825e5b2} + OpenTelemetry.Api + + + {ae3e3df5-4083-4c6e-a840-8271b0acde7e} + OpenTelemetry + + + diff --git a/examples/HttpServerExample/MyLabelSet.cs b/examples/HttpServerExample/MyLabelSet.cs index c06e45772a..ddf0f65282 100644 --- a/examples/HttpServerExample/MyLabelSet.cs +++ b/examples/HttpServerExample/MyLabelSet.cs @@ -1,41 +1,41 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using OpenTelemetry.Metrics; - -#pragma warning disable CS0618 - -namespace HttpServerExample -{ - public class MyLabelSet : LabelSet - { - public MyLabelSet(params (string name, string value)[] labels) - { - List> list = new List>(); - foreach (var label in labels) - { - list.Add(KeyValuePair.Create(label.name, label.value)); - } - - this.Labels = list; - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class MyLabelSet : LabelSet + { + public MyLabelSet(params (string name, string value)[] labels) + { + List> list = new List>(); + foreach (var label in labels) + { + list.Add(KeyValuePair.Create(label.name, label.value)); + } + + this.Labels = list; + } + } +} diff --git a/examples/HttpServerExample/MyLibrary.cs b/examples/HttpServerExample/MyLibrary.cs index 39d5543135..d1a152d07e 100644 --- a/examples/HttpServerExample/MyLibrary.cs +++ b/examples/HttpServerExample/MyLibrary.cs @@ -1,93 +1,93 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using OpenTelemetry.Metrics; - -#pragma warning disable CS0618 - -namespace HttpServerExample -{ - public class MyLibrary - { - Random rand = new Random(); - - public MyLibrary() - { - Meter meter = MeterProvider.Default.GetMeter("MyLibrary", "1.0.0"); - - var hostLabels = new MyLabelSet(("Host Name", GetHostName())); - - meter.CreateDoubleObserver("ServerRoomTemp", (k) => - { - double temp = this.GetServerRoomTemperature(); - k.Observe(temp, hostLabels); - }, true); - - meter.CreateDoubleObserver("SystemCpuUsage", (k) => - { - int cpu = this.GetSystemCpu(); - k.Observe(cpu, hostLabels); - }, true); - - meter.CreateDoubleObserver("ProcessCpuUsage", (k) => - { - int cpu = this.GetSystemCpu(); - int pid = this.GetProcessCpu(); - - var processLabels = new MyLabelSet( - ("Host Name", GetHostName()), - ("Process Id", $"{GetProcessId()}") - ); - k.Observe(cpu, processLabels); - }, true); - } - - public void Shutdown() - { - // Shutdown - } - - public string GetHostName() - { - return "MachineA"; - } - - public int GetProcessId() - { - var r = rand.Next(10); - return 1230 + r; - } - - public double GetServerRoomTemperature() - { - var r = rand.Next(10); - return 70.2 + r; - } - - public int GetSystemCpu() - { - var r = rand.Next(20); - return 30 + r; - } - - public int GetProcessCpu() - { - var r = rand.Next(20); - return 10 + r; - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using OpenTelemetry.Metrics; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class MyLibrary + { + Random rand = new Random(); + + public MyLibrary() + { + Meter meter = MeterProvider.Default.GetMeter("MyLibrary", "1.0.0"); + + var hostLabels = new MyLabelSet(("Host Name", GetHostName())); + + meter.CreateDoubleObserver("ServerRoomTemp", (k) => + { + double temp = this.GetServerRoomTemperature(); + k.Observe(temp, hostLabels); + }, true); + + meter.CreateDoubleObserver("SystemCpuUsage", (k) => + { + int cpu = this.GetSystemCpu(); + k.Observe(cpu, hostLabels); + }, true); + + meter.CreateDoubleObserver("ProcessCpuUsage", (k) => + { + int cpu = this.GetSystemCpu(); + int pid = this.GetProcessCpu(); + + var processLabels = new MyLabelSet( + ("Host Name", GetHostName()), + ("Process Id", $"{GetProcessId()}") + ); + k.Observe(cpu, processLabels); + }, true); + } + + public void Shutdown() + { + // Shutdown + } + + public string GetHostName() + { + return "MachineA"; + } + + public int GetProcessId() + { + var r = rand.Next(10); + return 1230 + r; + } + + public double GetServerRoomTemperature() + { + var r = rand.Next(10); + return 70.2 + r; + } + + public int GetSystemCpu() + { + var r = rand.Next(20); + return 30 + r; + } + + public int GetProcessCpu() + { + var r = rand.Next(20); + return 10 + r; + } + } +} diff --git a/examples/HttpServerExample/Program.cs b/examples/HttpServerExample/Program.cs index c43214346f..de9b940ecd 100644 --- a/examples/HttpServerExample/Program.cs +++ b/examples/HttpServerExample/Program.cs @@ -1,111 +1,111 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using OpenTelemetry.Metrics; -using OpenTelemetry.Metrics.Export; - -#pragma warning disable CS0618 - -namespace HttpServerExample -{ - public class Program - { - public static async Task Main(string[] args) - { - CancellationTokenSource cancelSrc = new CancellationTokenSource(); - - var client1Task = Client.StartClientTask("http://127.0.0.1:3000/Test1", 1000, cancelSrc.Token); - var client2Task = Client.StartClientTask("http://127.0.0.1:3000/Test2", 5000, cancelSrc.Token); - - // My Application - - var pipeFiveSeconds = OpenTelemetry.Sdk.CreateMeterProviderBuilder() - .SetPushInterval(TimeSpan.FromSeconds(5)) - .Build(); - - var pipeOneMinute = OpenTelemetry.Sdk.CreateMeterProviderBuilder() - .SetPushInterval(TimeSpan.FromSeconds(1)) - - // No way to specify which metrics I'm interested in! - .SetProcessor(new MyFilterProcessor("ServerRoomTemp")) - - .Build(); - - // How do I configure the Default provider? - // How do I tie Library to multiple pipeline? - MeterProvider.SetDefault(pipeOneMinute); - - // ======== - - var webserver = new WebServer(); - var webserverTask = webserver.StartServerTask("http://127.0.0.1:3000/", cancelSrc.Token); - - Console.WriteLine("Press [ENTER] to exit."); - var cmdline = Console.ReadLine(); - cancelSrc.Cancel(); - - webserver.Shutdown(); - await webserverTask; - - // ======== - - // There is no Shutdown() - // pipeFiveSeconds.Shutdown(); - // pipeOneMinute.Shutdown(); - } - - public class MyFilterProcessor : MetricProcessor - { - private static readonly object lockList = new object(); - private static List list = new List(); - - private string[] filters; - - public MyFilterProcessor(params string[] filters) - { - this.filters = filters; - } - - public override void FinishCollectionCycle(out IEnumerable metrics) - { - var oldList = Interlocked.Exchange(ref list, new List()); - metrics = oldList; - } - - public override void Process(Metric metric) - { - Console.WriteLine($"Processing {metric.MetricName}, count={metric.Data.Count}..."); - - foreach (var filter in this.filters) - { - if (metric.MetricName.Contains(filter)) - { - lock (lockList) - { - list.Add(metric); - } - - break; - } - } - } - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class Program + { + public static async Task Main(string[] args) + { + CancellationTokenSource cancelSrc = new CancellationTokenSource(); + + var client1Task = Client.StartClientTask("http://127.0.0.1:3000/Test1", 1000, cancelSrc.Token); + var client2Task = Client.StartClientTask("http://127.0.0.1:3000/Test2", 5000, cancelSrc.Token); + + // My Application + + var pipeFiveSeconds = OpenTelemetry.Sdk.CreateMeterProviderBuilder() + .SetPushInterval(TimeSpan.FromSeconds(5)) + .Build(); + + var pipeOneMinute = OpenTelemetry.Sdk.CreateMeterProviderBuilder() + .SetPushInterval(TimeSpan.FromSeconds(1)) + + // No way to specify which metrics I'm interested in! + .SetProcessor(new MyFilterProcessor("ServerRoomTemp")) + + .Build(); + + // How do I configure the Default provider? + // How do I tie Library to multiple pipeline? + MeterProvider.SetDefault(pipeOneMinute); + + // ======== + + var webserver = new WebServer(); + var webserverTask = webserver.StartServerTask("http://127.0.0.1:3000/", cancelSrc.Token); + + Console.WriteLine("Press [ENTER] to exit."); + var cmdline = Console.ReadLine(); + cancelSrc.Cancel(); + + webserver.Shutdown(); + await webserverTask; + + // ======== + + // There is no Shutdown() + // pipeFiveSeconds.Shutdown(); + // pipeOneMinute.Shutdown(); + } + + public class MyFilterProcessor : MetricProcessor + { + private static readonly object lockList = new object(); + private static List list = new List(); + + private string[] filters; + + public MyFilterProcessor(params string[] filters) + { + this.filters = filters; + } + + public override void FinishCollectionCycle(out IEnumerable metrics) + { + var oldList = Interlocked.Exchange(ref list, new List()); + metrics = oldList; + } + + public override void Process(Metric metric) + { + Console.WriteLine($"Processing {metric.MetricName}, count={metric.Data.Count}..."); + + foreach (var filter in this.filters) + { + if (metric.MetricName.Contains(filter)) + { + lock (lockList) + { + list.Add(metric); + } + + break; + } + } + } + } + } +} diff --git a/examples/HttpServerExample/WebServer.cs b/examples/HttpServerExample/WebServer.cs index 1670cefca5..f4c553dc08 100644 --- a/examples/HttpServerExample/WebServer.cs +++ b/examples/HttpServerExample/WebServer.cs @@ -1,162 +1,162 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Diagnostics; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; - -#pragma warning disable CS0618 - -namespace HttpServerExample -{ - public class WebServer - { - private MyLibrary library; - - private MeasureMetric duration; - private CounterMetric errorCount; - private CounterMetric incomingCount; - private CounterMetric outgoingCount; - - public WebServer() - { - // Initialize Web Server - - this.library = new MyLibrary(); - - Meter meter = MeterProvider.Default.GetMeter("MyServer", "1.0.0"); - - this.duration = meter.CreateInt64Measure("Server.Duration", true); - // How to tell it what unit the measurements are in? - - this.errorCount = meter.CreateInt64Counter("Server.Errors", true); - - this.incomingCount = meter.CreateInt64Counter("Server.Request.Incoming", true); - - this.outgoingCount = meter.CreateInt64Counter("Server.Request.Outgoing", true); - } - - public void Shutdown() - { - // Shutdown - } - - public Task StartServerTask(string prefix, CancellationToken token) - { - HttpListener listener = new HttpListener(); - - listener.Prefixes.Add(prefix); - - Task serverTask = Task.Run(async () => - { - Console.WriteLine("Server Started."); - - listener.Start(); - - Stopwatch sw = new Stopwatch(); - - while (!token.IsCancellationRequested) - { - var contextTask = listener.GetContextAsync(); - - try - { - Task.WaitAny(new Task[] { contextTask }, token); - } - catch (Exception) - { - // Do Nothing - } - - if (contextTask.IsCompletedSuccessfully && !contextTask.IsFaulted) - { - sw.Reset(); - - var context = await contextTask; - HttpListenerRequest request = context.Request; - - var requestLabels = new MyLabelSet( - ("Host Name", this.library.GetHostName()), - ("Process Id", this.library.GetProcessId().ToString()), - ("Method", request.HttpMethod), - ("Peer IP", request.RemoteEndPoint.Address.ToString()), - ("Port", request.Url.Port.ToString()) - ); - - this.incomingCount.Add(default(SpanContext), 1, requestLabels); - - // Parse request - - var path = request.Url.AbsolutePath; - - Console.WriteLine($"Server request for {path}"); - - // Format output - - string responseString = $"Hello world for {path}"; - byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString); - - HttpListenerResponse response = context.Response; - response.ContentLength64 = buffer.Length; - response.StatusCode = 200; - - // Return Response - - System.IO.Stream output = response.OutputStream; - output.Write(buffer, 0, buffer.Length); - output.Close(); - - // ======== - - outgoingCount.Add(default(SpanContext), 1, requestLabels); - - var elapsed = sw.ElapsedMilliseconds; - var labels = new MyLabelSet( - ("Host Name", this.library.GetHostName()), - ("Process Id", this.library.GetProcessId().ToString()), - ("Method", request.HttpMethod), - ("Peer IP", request.RemoteEndPoint.Address.ToString()), - ("Port", request.Url.Port.ToString()), - // Need to include Status Code - ("Status Code", response.StatusCode.ToString()) - ); - this.duration.Record(default(SpanContext), elapsed, labels); - } - else - { - // Count # of errors we have - - var labels = new MyLabelSet( - ("Host Name", this.library.GetHostName()), - ("Process Id", this.library.GetProcessId().ToString()) - ); - this.errorCount.Add(default(SpanContext), 1, labels); - } - } - - listener.Stop(); - - Console.WriteLine("Server Stopped."); - }); - - return serverTask; - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Diagnostics; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class WebServer + { + private MyLibrary library; + + private MeasureMetric duration; + private CounterMetric errorCount; + private CounterMetric incomingCount; + private CounterMetric outgoingCount; + + public WebServer() + { + // Initialize Web Server + + this.library = new MyLibrary(); + + Meter meter = MeterProvider.Default.GetMeter("MyServer", "1.0.0"); + + this.duration = meter.CreateInt64Measure("Server.Duration", true); + // How to tell it what unit the measurements are in? + + this.errorCount = meter.CreateInt64Counter("Server.Errors", true); + + this.incomingCount = meter.CreateInt64Counter("Server.Request.Incoming", true); + + this.outgoingCount = meter.CreateInt64Counter("Server.Request.Outgoing", true); + } + + public void Shutdown() + { + // Shutdown + } + + public Task StartServerTask(string prefix, CancellationToken token) + { + HttpListener listener = new HttpListener(); + + listener.Prefixes.Add(prefix); + + Task serverTask = Task.Run(async () => + { + Console.WriteLine("Server Started."); + + listener.Start(); + + Stopwatch sw = new Stopwatch(); + + while (!token.IsCancellationRequested) + { + var contextTask = listener.GetContextAsync(); + + try + { + Task.WaitAny(new Task[] { contextTask }, token); + } + catch (Exception) + { + // Do Nothing + } + + if (contextTask.IsCompletedSuccessfully && !contextTask.IsFaulted) + { + sw.Reset(); + + var context = await contextTask; + HttpListenerRequest request = context.Request; + + var requestLabels = new MyLabelSet( + ("Host Name", this.library.GetHostName()), + ("Process Id", this.library.GetProcessId().ToString()), + ("Method", request.HttpMethod), + ("Peer IP", request.RemoteEndPoint.Address.ToString()), + ("Port", request.Url.Port.ToString()) + ); + + this.incomingCount.Add(default(SpanContext), 1, requestLabels); + + // Parse request + + var path = request.Url.AbsolutePath; + + Console.WriteLine($"Server request for {path}"); + + // Format output + + string responseString = $"Hello world for {path}"; + byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString); + + HttpListenerResponse response = context.Response; + response.ContentLength64 = buffer.Length; + response.StatusCode = 200; + + // Return Response + + System.IO.Stream output = response.OutputStream; + output.Write(buffer, 0, buffer.Length); + output.Close(); + + // ======== + + outgoingCount.Add(default(SpanContext), 1, requestLabels); + + var elapsed = sw.ElapsedMilliseconds; + var labels = new MyLabelSet( + ("Host Name", this.library.GetHostName()), + ("Process Id", this.library.GetProcessId().ToString()), + ("Method", request.HttpMethod), + ("Peer IP", request.RemoteEndPoint.Address.ToString()), + ("Port", request.Url.Port.ToString()), + // Need to include Status Code + ("Status Code", response.StatusCode.ToString()) + ); + this.duration.Record(default(SpanContext), elapsed, labels); + } + else + { + // Count # of errors we have + + var labels = new MyLabelSet( + ("Host Name", this.library.GetHostName()), + ("Process Id", this.library.GetProcessId().ToString()) + ); + this.errorCount.Add(default(SpanContext), 1, labels); + } + } + + listener.Stop(); + + Console.WriteLine("Server Stopped."); + }); + + return serverTask; + } + } +} diff --git a/examples/HttpServerExample/readme.md b/examples/HttpServerExample/readme.md index 7b1df457f5..3929fba808 100644 --- a/examples/HttpServerExample/readme.md +++ b/examples/HttpServerExample/readme.md @@ -1,78 +1,78 @@ -# OpenTelemetry HTTP Server Demo - -## Scenario - -The _OpenTelemetry HTTP Server_ demo shows how a library developer X could use -metrics API to instrument a library, and how the application developer Y can -configure the library to use OpenTelemetry SDK in a final application. X and Y -are working for different companies and they don't communicate. The demo has two -parts - the library `io.opentelemetry.example.httpserver.instrumented` (owned by -X) and the server app `io.opentelemetry.example.httpserver.application` (owned -by Y): - -* How developer X could instrument the library code in a vendor agnostic way - * Performance is critical for X - * X doesn't know which time series and which dimension will Y pick - * X doesn't know the aggregation time window, nor the final destination of the - metrics -* How developer Y could configure the SDK and exporter - * How should Y hook up the metrics SDK with the library - * How should Y configure the time window(s) and destination(s) - * How should Y pick the time series and the dimensions - -### Library Requirements - -The library developer (developer X) will expose the following metrics out of -box: - -#### Process CPU Usage - -| Host Name | Process ID | CPU% [0.0, 100.0] | -| --------- | ---------- | ----------------- | -| MachineA | 1234 | 15.3 | - -#### System CPU Usage - -| Host Name | CPU% [0, 100] | -| --------- | ------------- | -| MachineA | 30 | - -#### Server Room Temperature - -| Host Name | Temperature (F) | -| --------- | --------------- | -| MachineA | 65.3 | - -#### HTTP Server Duration - -| Host Name | Process ID | HTTP Method | HTTP Host | HTTP Status Code | HTTP Flavor | Peer IP | Peer Port | Host IP | Host Port | Duration (ms) | -| --------- | ---------- | ----------- | --------- | ---------------- | ----------- | --------- | --------- | --------- | --------- | ------------- | -| MachineA | 1234 | GET | otel.org | 200 | 1.1 | 127.0.0.1 | 51327 | 127.0.0.1 | 80 | 8.5 | -| MachineA | 1234 | POST | otel.org | 304 | 1.1 | 127.0.0.1 | 51328 | 127.0.0.1 | 80 | 100.0 | - -### Application Requirements - -The application owner (developer Y) would only want the following metrics: - -* [System CPU Usage](#system-cpu-usage) reported every 5 seconds -* [Server Room Temperature](#server-room-temperature) reported every minute -* [HTTP Server Duration](#http-server-duration), reported every 5 seconds, with - a subset of the dimensions: - * Host Name - * HTTP Method - * HTTP Host - * HTTP Status Code - * 90%, 95%, 99% and 99.9% latency -* HTTP request counters, reported every 5 seconds: - * Total number of received HTTP requests - * Total number of finished HTTP requests - * Number of ongoing HTTP requests (concurrent HTTP requests) - - | Host Name | Process ID | HTTP Host | Received Requests | Finished Requests | Concurrent Requests | - | --------- | ---------- | --------- | ----------------- | ----------------- | ------------------- | - | MachineA | 1234 | otel.org | 630 | 601 | 29 | - | MachineA | 5678 | otel.org | 1005 | 1001 | 4 | -* Exception samples (examplar) - in case HTTP 5xx happened, developer Y would - want to see a sample request with all the dimensions (IP, Port, etc.) - -## How to run +# OpenTelemetry HTTP Server Demo + +## Scenario + +The _OpenTelemetry HTTP Server_ demo shows how a library developer X could use +metrics API to instrument a library, and how the application developer Y can +configure the library to use OpenTelemetry SDK in a final application. X and Y +are working for different companies and they don't communicate. The demo has two +parts - the library `io.opentelemetry.example.httpserver.instrumented` (owned by +X) and the server app `io.opentelemetry.example.httpserver.application` (owned +by Y): + +* How developer X could instrument the library code in a vendor agnostic way + * Performance is critical for X + * X doesn't know which time series and which dimension will Y pick + * X doesn't know the aggregation time window, nor the final destination of the + metrics +* How developer Y could configure the SDK and exporter + * How should Y hook up the metrics SDK with the library + * How should Y configure the time window(s) and destination(s) + * How should Y pick the time series and the dimensions + +### Library Requirements + +The library developer (developer X) will expose the following metrics out of +box: + +#### Process CPU Usage + +| Host Name | Process ID | CPU% [0.0, 100.0] | +| --------- | ---------- | ----------------- | +| MachineA | 1234 | 15.3 | + +#### System CPU Usage + +| Host Name | CPU% [0, 100] | +| --------- | ------------- | +| MachineA | 30 | + +#### Server Room Temperature + +| Host Name | Temperature (F) | +| --------- | --------------- | +| MachineA | 65.3 | + +#### HTTP Server Duration + +| Host Name | Process ID | HTTP Method | HTTP Host | HTTP Status Code | HTTP Flavor | Peer IP | Peer Port | Host IP | Host Port | Duration (ms) | +| --------- | ---------- | ----------- | --------- | ---------------- | ----------- | --------- | --------- | --------- | --------- | ------------- | +| MachineA | 1234 | GET | otel.org | 200 | 1.1 | 127.0.0.1 | 51327 | 127.0.0.1 | 80 | 8.5 | +| MachineA | 1234 | POST | otel.org | 304 | 1.1 | 127.0.0.1 | 51328 | 127.0.0.1 | 80 | 100.0 | + +### Application Requirements + +The application owner (developer Y) would only want the following metrics: + +* [System CPU Usage](#system-cpu-usage) reported every 5 seconds +* [Server Room Temperature](#server-room-temperature) reported every minute +* [HTTP Server Duration](#http-server-duration), reported every 5 seconds, with + a subset of the dimensions: + * Host Name + * HTTP Method + * HTTP Host + * HTTP Status Code + * 90%, 95%, 99% and 99.9% latency +* HTTP request counters, reported every 5 seconds: + * Total number of received HTTP requests + * Total number of finished HTTP requests + * Number of ongoing HTTP requests (concurrent HTTP requests) + + | Host Name | Process ID | HTTP Host | Received Requests | Finished Requests | Concurrent Requests | + | --------- | ---------- | --------- | ----------------- | ----------------- | ------------------- | + | MachineA | 1234 | otel.org | 630 | 601 | 29 | + | MachineA | 5678 | otel.org | 1005 | 1001 | 4 | +* Exception samples (examplar) - in case HTTP 5xx happened, developer Y would + want to see a sample request with all the dimensions (IP, Port, etc.) + +## How to run From 8b019537524443fcf53873479e671cac1fcf579e Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Tue, 16 Feb 2021 18:11:44 -0800 Subject: [PATCH 14/17] Fix sanditycheck.py issues --- examples/GroceryExample/GroceryExample.csproj | 2 +- examples/HttpServerExample/WebServer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/GroceryExample/GroceryExample.csproj b/examples/GroceryExample/GroceryExample.csproj index c992da82e1..006dbfb420 100644 --- a/examples/GroceryExample/GroceryExample.csproj +++ b/examples/GroceryExample/GroceryExample.csproj @@ -14,4 +14,4 @@ OpenTelemetry - \ No newline at end of file + diff --git a/examples/HttpServerExample/WebServer.cs b/examples/HttpServerExample/WebServer.cs index f4c553dc08..598fed6de4 100644 --- a/examples/HttpServerExample/WebServer.cs +++ b/examples/HttpServerExample/WebServer.cs @@ -55,7 +55,7 @@ public WebServer() public void Shutdown() { - // Shutdown + // Shutdown } public Task StartServerTask(string prefix, CancellationToken token) From 141f684e7403415da363c5bd52d74bbf111974cc Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Wed, 17 Feb 2021 14:35:55 -0800 Subject: [PATCH 15/17] Tie Default provider to SDK --- .../GroceryExample/Misc/MyMetricExporter.cs | 21 ++++++++++++++++++- examples/GroceryExample/Program.cs | 8 +++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/examples/GroceryExample/Misc/MyMetricExporter.cs b/examples/GroceryExample/Misc/MyMetricExporter.cs index c67fa5bc79..658aa7121b 100644 --- a/examples/GroceryExample/Misc/MyMetricExporter.cs +++ b/examples/GroceryExample/Misc/MyMetricExporter.cs @@ -33,13 +33,32 @@ public override Task ExportAsync(IEnumerable metrics, Canc { StringBuilder sb = new StringBuilder(); + sb.AppendLine("Exporting..."); foreach (var m in metrics) { sb.AppendLine($"[{m.MetricNamespace}:{m.MetricName}]"); foreach (var data in m.Data) { - sb.Append(" Labels: "); + sb.Append(" "); + + string val = "-"; + if (data is DoubleSumData doublesum) + { + val = $"Sum={doublesum.Sum}"; + } + else if (data is Int64SumData int64sum) + { + val = $"Sum={int64sum.Sum}"; + } + else + { + val = data.ToString(); + } + + sb.Append($"Data: {val}, "); + + sb.Append("Labels: "); foreach (var l in data.Labels) { sb.Append($"{l.Key}={l.Value}, "); diff --git a/examples/GroceryExample/Program.cs b/examples/GroceryExample/Program.cs index e6a1c8399e..1a63d7eaef 100644 --- a/examples/GroceryExample/Program.cs +++ b/examples/GroceryExample/Program.cs @@ -16,6 +16,7 @@ using System; using System.Threading.Tasks; +using OpenTelemetry.Metrics; #pragma warning disable CS0618 @@ -29,11 +30,18 @@ public static void Main(string[] args) var sdk = OpenTelemetry.Sdk.CreateMeterProviderBuilder() .SetPushInterval(TimeSpan.FromMilliseconds(1000)) + + // Need to have a processor to move Metrics through pipeline .SetProcessor(new MyMetricProcessor()) + .SetExporter(new MyMetricExporter()) + .Build() ; + // Need to set for the Default provider + MeterProvider.SetDefault(sdk); + var store = new GroceryStore("Portland"); store.ProcessOrder("customerA", ("potato", 2), ("tomato", 3)); From 1947c3c4a683276ba92a4826fb119b82a85ae66e Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Fri, 19 Feb 2021 11:30:21 -0800 Subject: [PATCH 16/17] Refactor per current API/SDK recommendation --- examples/GroceryExample/GroceryStore.cs | 32 +++++--- .../GroceryExample/Misc/MyMetricProcessor.cs | 39 ---------- examples/GroceryExample/MyLabelSet.cs | 39 ---------- examples/GroceryExample/Program.cs | 3 +- examples/HttpServerExample/MyLabelSet.cs | 41 ---------- examples/HttpServerExample/MyLibrary.cs | 47 +++++++---- .../HttpServerExample/MyMetricExporter.cs | 77 +++++++++++++++++++ examples/HttpServerExample/Program.cs | 10 ++- examples/HttpServerExample/WebServer.cs | 57 ++++++++------ 9 files changed, 169 insertions(+), 176 deletions(-) delete mode 100644 examples/GroceryExample/Misc/MyMetricProcessor.cs delete mode 100644 examples/GroceryExample/MyLabelSet.cs delete mode 100644 examples/HttpServerExample/MyLabelSet.cs create mode 100644 examples/HttpServerExample/MyMetricExporter.cs diff --git a/examples/GroceryExample/GroceryStore.cs b/examples/GroceryExample/GroceryStore.cs index fba33a1aae..0fa7d7794c 100644 --- a/examples/GroceryExample/GroceryStore.cs +++ b/examples/GroceryExample/GroceryStore.cs @@ -32,6 +32,8 @@ public class GroceryStore private string storeName; + private Meter meter; + private CounterMetric itemCounter; private CounterMetric cashCounter; @@ -44,14 +46,16 @@ public GroceryStore(string storeName) // Setup Metrics - Meter meter = MeterProvider.Default.GetMeter("GroceryStore", "1.0.0"); + this.meter = MeterProvider.Default.GetMeter("GroceryStore", "1.0.0"); - this.itemCounter = meter.CreateInt64Counter("item_counter"); + this.itemCounter = this.meter.CreateInt64Counter("item_counter"); - this.cashCounter = meter.CreateDoubleCounter("cash_counter"); + this.cashCounter = this.meter.CreateDoubleCounter("cash_counter"); - var labels = new MyLabelSet( - new KeyValuePair("Store", "Portland")); + var labels = this.meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Store", "Portland"), + }); this.boundCashCounter = this.cashCounter.Bind(labels); } @@ -66,19 +70,23 @@ public void ProcessOrder(string customer, params (string name, int qty)[] items) // Record Metric - var labels = new MyLabelSet( - new KeyValuePair("Store", "Portland"), - new KeyValuePair("Customer", customer), - new KeyValuePair("Item", item.name)); + var labels = this.meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Store", "Portland"), + KeyValuePair.Create("Customer", customer), + KeyValuePair.Create("Item", item.name), + }); this.itemCounter.Add(default(SpanContext), item.qty, labels); } // Record Metric - var labels2 = new MyLabelSet( - new KeyValuePair("Store", "Portland"), - new KeyValuePair("Customer", customer)); + var labels2 = this.meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Store", "Portland"), + KeyValuePair.Create("Customer", customer), + }); this.cashCounter.Add(default(SpanContext), totalPrice, labels2); diff --git a/examples/GroceryExample/Misc/MyMetricProcessor.cs b/examples/GroceryExample/Misc/MyMetricProcessor.cs deleted file mode 100644 index e092c0348d..0000000000 --- a/examples/GroceryExample/Misc/MyMetricProcessor.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Collections.Generic; -using System.Threading; -using OpenTelemetry.Metrics.Export; - -#pragma warning disable CS0618 - -namespace GroceryExample -{ - public class MyMetricProcessor : MetricProcessor - { - private List items = new List(); - - public override void FinishCollectionCycle(out IEnumerable metrics) - { - metrics = Interlocked.Exchange(ref this.items, new List()); - } - - public override void Process(Metric metric) - { - this.items.Add(metric); - } - } -} diff --git a/examples/GroceryExample/MyLabelSet.cs b/examples/GroceryExample/MyLabelSet.cs deleted file mode 100644 index fe8ee4dfda..0000000000 --- a/examples/GroceryExample/MyLabelSet.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Collections.Generic; -using OpenTelemetry.Metrics; - -#pragma warning disable CS0618 - -namespace GroceryExample -{ - public class MyLabelSet : LabelSet - { - public MyLabelSet(params KeyValuePair[] labels) - { - List> list = new List>(); - foreach (var kv in labels) - { - list.Add(kv); - } - - this.Labels = list; - } - - public override IEnumerable> Labels { get; set; } = System.Linq.Enumerable.Empty>(); - } -} diff --git a/examples/GroceryExample/Program.cs b/examples/GroceryExample/Program.cs index 1a63d7eaef..f83007ac62 100644 --- a/examples/GroceryExample/Program.cs +++ b/examples/GroceryExample/Program.cs @@ -17,6 +17,7 @@ using System; using System.Threading.Tasks; using OpenTelemetry.Metrics; +using OpenTelemetry.Metrics.Export; #pragma warning disable CS0618 @@ -32,7 +33,7 @@ public static void Main(string[] args) .SetPushInterval(TimeSpan.FromMilliseconds(1000)) // Need to have a processor to move Metrics through pipeline - .SetProcessor(new MyMetricProcessor()) + .SetProcessor(new UngroupedBatcher()) .SetExporter(new MyMetricExporter()) diff --git a/examples/HttpServerExample/MyLabelSet.cs b/examples/HttpServerExample/MyLabelSet.cs deleted file mode 100644 index ddf0f65282..0000000000 --- a/examples/HttpServerExample/MyLabelSet.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using OpenTelemetry.Metrics; - -#pragma warning disable CS0618 - -namespace HttpServerExample -{ - public class MyLabelSet : LabelSet - { - public MyLabelSet(params (string name, string value)[] labels) - { - List> list = new List>(); - foreach (var label in labels) - { - list.Add(KeyValuePair.Create(label.name, label.value)); - } - - this.Labels = list; - } - } -} diff --git a/examples/HttpServerExample/MyLibrary.cs b/examples/HttpServerExample/MyLibrary.cs index d1a152d07e..ea4f26ee2c 100644 --- a/examples/HttpServerExample/MyLibrary.cs +++ b/examples/HttpServerExample/MyLibrary.cs @@ -15,6 +15,7 @@ // using System; +using System.Collections.Generic; using OpenTelemetry.Metrics; #pragma warning disable CS0618 @@ -23,37 +24,51 @@ namespace HttpServerExample { public class MyLibrary { - Random rand = new Random(); + private Random rand = new Random(); public MyLibrary() { Meter meter = MeterProvider.Default.GetMeter("MyLibrary", "1.0.0"); - var hostLabels = new MyLabelSet(("Host Name", GetHostName())); + var hostLabels = meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Host Name", this.GetHostName()), + }); - meter.CreateDoubleObserver("ServerRoomTemp", (k) => + meter.CreateDoubleObserver( + "ServerRoomTemp", + (k) => { double temp = this.GetServerRoomTemperature(); k.Observe(temp, hostLabels); - }, true); + }, + true); - meter.CreateDoubleObserver("SystemCpuUsage", (k) => + meter.CreateDoubleObserver( + "SystemCpuUsage", + (k) => { int cpu = this.GetSystemCpu(); k.Observe(cpu, hostLabels); - }, true); + }, + true); - meter.CreateDoubleObserver("ProcessCpuUsage", (k) => + meter.CreateDoubleObserver( + "ProcessCpuUsage", + (k) => { int cpu = this.GetSystemCpu(); int pid = this.GetProcessCpu(); - var processLabels = new MyLabelSet( - ("Host Name", GetHostName()), - ("Process Id", $"{GetProcessId()}") - ); + var processLabels = meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Host Name", this.GetHostName()), + KeyValuePair.Create("Process Id", $"{this.GetProcessId()}"), + }); + k.Observe(cpu, processLabels); - }, true); + }, + true); } public void Shutdown() @@ -68,25 +83,25 @@ public string GetHostName() public int GetProcessId() { - var r = rand.Next(10); + var r = this.rand.Next(10); return 1230 + r; } public double GetServerRoomTemperature() { - var r = rand.Next(10); + var r = this.rand.Next(10); return 70.2 + r; } public int GetSystemCpu() { - var r = rand.Next(20); + var r = this.rand.Next(20); return 30 + r; } public int GetProcessCpu() { - var r = rand.Next(20); + var r = this.rand.Next(20); return 10 + r; } } diff --git a/examples/HttpServerExample/MyMetricExporter.cs b/examples/HttpServerExample/MyMetricExporter.cs new file mode 100644 index 0000000000..4c07cd30e5 --- /dev/null +++ b/examples/HttpServerExample/MyMetricExporter.cs @@ -0,0 +1,77 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class MyMetricExporter : MetricExporter + { + public override Task ExportAsync(IEnumerable metrics, CancellationToken cancellationToken) + { + return Task.Run(() => + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("Exporting..."); + foreach (var m in metrics) + { + sb.AppendLine($"[{m.MetricNamespace}:{m.MetricName}]"); + + foreach (var data in m.Data) + { + sb.Append(" "); + + string val = "-"; + if (data is DoubleSumData doublesum) + { + val = $"Sum={doublesum.Sum}"; + } + else if (data is Int64SumData int64sum) + { + val = $"Sum={int64sum.Sum}"; + } + else + { + val = data.ToString(); + } + + sb.Append($"Data: {val}, "); + + sb.Append("Labels: "); + foreach (var l in data.Labels) + { + sb.Append($"{l.Key}={l.Value}, "); + } + + sb.AppendLine(); + } + } + + Console.WriteLine(sb.ToString()); + + return ExportResult.Success; + }); + } + } +} diff --git a/examples/HttpServerExample/Program.cs b/examples/HttpServerExample/Program.cs index de9b940ecd..81ee582dda 100644 --- a/examples/HttpServerExample/Program.cs +++ b/examples/HttpServerExample/Program.cs @@ -38,14 +38,18 @@ public static async Task Main(string[] args) var pipeFiveSeconds = OpenTelemetry.Sdk.CreateMeterProviderBuilder() .SetPushInterval(TimeSpan.FromSeconds(5)) + .SetProcessor(new UngroupedBatcher()) + .SetExporter(new MyMetricExporter()) .Build(); var pipeOneMinute = OpenTelemetry.Sdk.CreateMeterProviderBuilder() .SetPushInterval(TimeSpan.FromSeconds(1)) - // No way to specify which metrics I'm interested in! + // No way to select which metrics I'm interested in! + // How to add multiple processors? .SetProcessor(new MyFilterProcessor("ServerRoomTemp")) + .SetExporter(new MyMetricExporter()) .Build(); // How do I configure the Default provider? @@ -73,7 +77,7 @@ public static async Task Main(string[] args) public class MyFilterProcessor : MetricProcessor { - private static readonly object lockList = new object(); + private static readonly object LockList = new object(); private static List list = new List(); private string[] filters; @@ -97,7 +101,7 @@ public override void Process(Metric metric) { if (metric.MetricName.Contains(filter)) { - lock (lockList) + lock (LockList) { list.Add(metric); } diff --git a/examples/HttpServerExample/WebServer.cs b/examples/HttpServerExample/WebServer.cs index 598fed6de4..db5053d529 100644 --- a/examples/HttpServerExample/WebServer.cs +++ b/examples/HttpServerExample/WebServer.cs @@ -15,6 +15,7 @@ // using System; +using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Threading; @@ -30,6 +31,7 @@ public class WebServer { private MyLibrary library; + private Meter meter; private MeasureMetric duration; private CounterMetric errorCount; private CounterMetric incomingCount; @@ -41,16 +43,17 @@ public WebServer() this.library = new MyLibrary(); - Meter meter = MeterProvider.Default.GetMeter("MyServer", "1.0.0"); + this.meter = MeterProvider.Default.GetMeter("MyServer", "1.0.0"); - this.duration = meter.CreateInt64Measure("Server.Duration", true); // How to tell it what unit the measurements are in? + // How to set the Description? + this.duration = this.meter.CreateInt64Measure("Server.Duration", true); - this.errorCount = meter.CreateInt64Counter("Server.Errors", true); + this.errorCount = this.meter.CreateInt64Counter("Server.Errors", true); - this.incomingCount = meter.CreateInt64Counter("Server.Request.Incoming", true); + this.incomingCount = this.meter.CreateInt64Counter("Server.Request.Incoming", true); - this.outgoingCount = meter.CreateInt64Counter("Server.Request.Outgoing", true); + this.outgoingCount = this.meter.CreateInt64Counter("Server.Request.Outgoing", true); } public void Shutdown() @@ -92,13 +95,14 @@ public Task StartServerTask(string prefix, CancellationToken token) var context = await contextTask; HttpListenerRequest request = context.Request; - var requestLabels = new MyLabelSet( - ("Host Name", this.library.GetHostName()), - ("Process Id", this.library.GetProcessId().ToString()), - ("Method", request.HttpMethod), - ("Peer IP", request.RemoteEndPoint.Address.ToString()), - ("Port", request.Url.Port.ToString()) - ); + var requestLabels = this.meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Host Name", this.library.GetHostName()), + KeyValuePair.Create("Process Id", this.library.GetProcessId().ToString()), + KeyValuePair.Create("Method", request.HttpMethod), + KeyValuePair.Create("Peer IP", request.RemoteEndPoint.Address.ToString()), + KeyValuePair.Create("Port", request.Url.Port.ToString()), + }); this.incomingCount.Add(default(SpanContext), 1, requestLabels); @@ -125,28 +129,31 @@ public Task StartServerTask(string prefix, CancellationToken token) // ======== - outgoingCount.Add(default(SpanContext), 1, requestLabels); + this.outgoingCount.Add(default(SpanContext), 1, requestLabels); var elapsed = sw.ElapsedMilliseconds; - var labels = new MyLabelSet( - ("Host Name", this.library.GetHostName()), - ("Process Id", this.library.GetProcessId().ToString()), - ("Method", request.HttpMethod), - ("Peer IP", request.RemoteEndPoint.Address.ToString()), - ("Port", request.Url.Port.ToString()), + var labels = this.meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Host Name", this.library.GetHostName()), + KeyValuePair.Create("Process Id", this.library.GetProcessId().ToString()), + KeyValuePair.Create("Method", request.HttpMethod), + KeyValuePair.Create("Peer IP", request.RemoteEndPoint.Address.ToString()), + KeyValuePair.Create("Port", request.Url.Port.ToString()), + // Need to include Status Code - ("Status Code", response.StatusCode.ToString()) - ); + KeyValuePair.Create("Status Code", response.StatusCode.ToString()), + }); this.duration.Record(default(SpanContext), elapsed, labels); } else { // Count # of errors we have - var labels = new MyLabelSet( - ("Host Name", this.library.GetHostName()), - ("Process Id", this.library.GetProcessId().ToString()) - ); + var labels = this.meter.GetLabelSet(new List>() + { + KeyValuePair.Create("Host Name", this.library.GetHostName()), + KeyValuePair.Create("Process Id", this.library.GetProcessId().ToString()), + }); this.errorCount.Add(default(SpanContext), 1, labels); } } From cb9845972ff5d082abc92c39486104c4a7d238a5 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Fri, 19 Feb 2021 11:42:12 -0800 Subject: [PATCH 17/17] Annoying we don't take CRLF as line ending! --- .../HttpServerExample/MyMetricExporter.cs | 154 +++++++++--------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/examples/HttpServerExample/MyMetricExporter.cs b/examples/HttpServerExample/MyMetricExporter.cs index 4c07cd30e5..b67973efd9 100644 --- a/examples/HttpServerExample/MyMetricExporter.cs +++ b/examples/HttpServerExample/MyMetricExporter.cs @@ -1,77 +1,77 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using OpenTelemetry.Metrics.Export; - -#pragma warning disable CS0618 - -namespace HttpServerExample -{ - public class MyMetricExporter : MetricExporter - { - public override Task ExportAsync(IEnumerable metrics, CancellationToken cancellationToken) - { - return Task.Run(() => - { - StringBuilder sb = new StringBuilder(); - - sb.AppendLine("Exporting..."); - foreach (var m in metrics) - { - sb.AppendLine($"[{m.MetricNamespace}:{m.MetricName}]"); - - foreach (var data in m.Data) - { - sb.Append(" "); - - string val = "-"; - if (data is DoubleSumData doublesum) - { - val = $"Sum={doublesum.Sum}"; - } - else if (data is Int64SumData int64sum) - { - val = $"Sum={int64sum.Sum}"; - } - else - { - val = data.ToString(); - } - - sb.Append($"Data: {val}, "); - - sb.Append("Labels: "); - foreach (var l in data.Labels) - { - sb.Append($"{l.Key}={l.Value}, "); - } - - sb.AppendLine(); - } - } - - Console.WriteLine(sb.ToString()); - - return ExportResult.Success; - }); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Metrics.Export; + +#pragma warning disable CS0618 + +namespace HttpServerExample +{ + public class MyMetricExporter : MetricExporter + { + public override Task ExportAsync(IEnumerable metrics, CancellationToken cancellationToken) + { + return Task.Run(() => + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("Exporting..."); + foreach (var m in metrics) + { + sb.AppendLine($"[{m.MetricNamespace}:{m.MetricName}]"); + + foreach (var data in m.Data) + { + sb.Append(" "); + + string val = "-"; + if (data is DoubleSumData doublesum) + { + val = $"Sum={doublesum.Sum}"; + } + else if (data is Int64SumData int64sum) + { + val = $"Sum={int64sum.Sum}"; + } + else + { + val = data.ToString(); + } + + sb.Append($"Data: {val}, "); + + sb.Append("Labels: "); + foreach (var l in data.Labels) + { + sb.Append($"{l.Key}={l.Value}, "); + } + + sb.AppendLine(); + } + } + + Console.WriteLine(sb.ToString()); + + return ExportResult.Success; + }); + } + } +}