Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActivityListener API Improvement Proposal #2

Open
CodeBlanch opened this issue Jun 3, 2020 · 0 comments
Open

ActivityListener API Improvement Proposal #2

CodeBlanch opened this issue Jun 3, 2020 · 0 comments

Comments

@CodeBlanch
Copy link
Owner

CodeBlanch commented Jun 3, 2020

I thought we could discuss here before moving to dotnet/runtime, if we decide to move forward at all.

Background and Motivation

I have been saying the ActivityListener API is confusing and hard to implement from the perspective of OpenTelemetry, here's a scratch attempt at improving it.

/cc @cijothomas @MikeGoldsmith @pjanotti @tarekgh @noahfalk

Proposed API

    public sealed class ActivitySource : IDisposable
    {
        // Only showing the signature changes, everything else is the same...
        public Activity? StartActivity(string name, ActivityKind kind, ActivityContext parentContext, Action<IDictionary<string, string?>>? populateTagsAction = null, IEnumerable<ActivityLink>? links = null, DateTimeOffset startTime = default) { return null; }
        public Activity? StartActivity(string name, ActivityKind kind, string parentId, Action<IDictionary<string, string?>>? populateTagsAction = null, IEnumerable<ActivityLink>? links = null, DateTimeOffset startTime = default) { return null; }
    }
    public enum ActivitySamplingDecision
    {
        None,
        PropagateOnly,
        Sample
    }
    public readonly struct ActivityCreationOptions
    {
        public ActivitySource Source { get; }
        public string Name { get; }
        public ActivityKind Kind { get; }
        public ActivityParentState Parent { get; }
        public IEnumerable<KeyValuePair<string, string?>>? Tags { get; }
        public IEnumerable<ActivityLink>? Links { get; }
    }
    public readonly struct ActivityParentState
    {
        public string? Id { get; }
        public ActivityContext? ActivityContext { get; }
    }
    public sealed class ActivityListener : IDisposable
    {
        public ActivityListener() { ActivitySampler = new PropagationOnlyActivitySampler(); }
        public ActivityListener(ActivitySampler activitySampler) { }
        public ActivitySampler ActivitySampler { get; }
        public Action<Activity>? ActivityStarted { get; set; }
        public Action<Activity>? ActivityStopped { get; set; }
        public Func<ActivitySource, bool>? ShouldListenTo { get; set; }
        public void Dispose() { }
    }
    public abstract class ActivitySampler
    {
        protected abstract bool RequiresTagsForSampling { get; }
        public abstract ActivitySamplingDecision ShouldSample(ref ActivityCreationOptions options);
    }
    public class PropagationOnlyActivitySampler : ActivitySampler
    {
        protected override bool RequiresTagsForSampling => false;
        public override ActivitySamplingDecision ShouldSample(ref ActivityCreationOptions options) => ActivitySamplingDecision.PropagateOnly;
    }
  • I introduced the concept of an ActivitySampler. The callbacks were confusing. I figured, call a spade a spade.
  • If you don't specify an ActivitySampler, we provide a default one called PropagationOnlyActivitySampler. We could also provide some other basic things OOB like Always, Never, Probability samplers?
  • Today we have a gray area when calling StartActivity, do we or do we not provide tags? In order to improve that I switched it to a callback that can be used by the sampler to load tags if it needs them. ActivitySampler indicates what it needs using RequiresTagsForSampling flag. If set to true, we'll attempt to populate tags onto ActivityCreationOptions before calling the sampler.
  • Replaced ActivityDataRequest with ActivitySamplingDecision and removed one of the states.
  • This also solves the "should I sample external activity" thing by allowing you to do: ActivityListener.ActivitySampler.ShouldSample(...) invocation manually, if you want to.

Usage Examples

Basic usage. Should look almost exactly as it does today, only change is you aren't forced to use the callbacks. Provide a sampler or use the default one is the idea.

        public static void SimpleMethod()
        {
            ActivitySource myActivitySource = new ActivitySource("MyActivitySource");

            using ActivityListener Listener = new ActivityListener
            {
                ShouldListenTo = (activitySource) => activitySource.Name == "MyActivitySource"
            };

            using Activity activity = myActivitySource.StartActivity("MyServerOperation", ActivityKind.Server);

            if (activity?.IsAllDataRequested == true)
            {
                activity.AddTag("custom.tag", Guid.NewGuid().ToString("n"));
            }
        }

More advanced usage. This would be what OpenTelemetry does.

        public class DeferToParentOtherwiseProbabilitySampler : ActivitySampler
        {
            protected override bool RequiresTagsForSampling => false;
            public override ActivitySamplingDecision ShouldSample(ref ActivityCreationOptions options)
            {
                // If we have a parent, respect its sampling decision.
                if (options.Parent.ActivityContext.HasValue)
                {
                    return options.Parent.ActivityContext.Value.TraceFlags == ActivityTraceFlags.Recorded
                        ? ActivitySamplingDecision.Sample
                        : ActivitySamplingDecision.PropagateOnly;
                }

                // If we only have a parent Id, let's propagate.
                if (!string.IsNullOrEmpty(options.Parent.Id))
                    return ActivitySamplingDecision.PropagateOnly;

                // If we're at the root, let's determine based on probability.
                return ShouldSampleBasedOnProbability()
                    ? ActivitySamplingDecision.Sample
                    : ActivitySamplingDecision.PropagateOnly;
            }

            private bool ShouldSampleBasedOnProbability()
            {
                return true; // some real logic goes here...
            }
        }

        public static void ParentBasedSamplingMethod()
        {
            ActivitySource myActivitySource = new ActivitySource("MyActivitySource");

            using ActivityListener Listener = new ActivityListener(new DeferToParentOtherwiseProbabilitySampler())
            {
                ShouldListenTo = (activitySource) => activitySource.Name == "MyActivitySource"
            };

            using Activity activity = myActivitySource.StartActivity("MyServerOperation", ActivityKind.Server);

            if (activity?.IsAllDataRequested == true)
            {
                activity.AddTag("custom.tag", Guid.NewGuid().ToString("n"));
            }
        }

Really advanced usage. Sampling based on tag values. No known use case? Is called out in the OT spec.

        public class AdvancedSampler : ActivitySampler
        {
            protected override bool RequiresTagsForSampling => true;
            public override ActivitySamplingDecision ShouldSample(ref ActivityCreationOptions options)
            {
                return options.Tags?.Any(t => t.Key == "custom.prop" && t.Value == "1") == true
                    ? ActivitySamplingDecision.Sample
                    : ActivitySamplingDecision.PropagateOnly;
            }
        }

        public static void TagBasedSamplingMethod()
        {
            ActivitySource myActivitySource = new ActivitySource("MyActivitySource");

            using ActivityListener Listener = new ActivityListener(new AdvancedSampler())
            {
                ShouldListenTo = (activitySource) => activitySource.Name == "MyActivitySource"
            };

            using Activity activity = myActivitySource.StartActivity(
                "MyServerOperation",
                ActivityKind.Server,
                parentContext: default,
                (IDictionary<string, string?> tags) =>
                {
                    tags["custom.prop"] = "1";
                },
                null,
                default);

            if (activity?.IsAllDataRequested == true)
            {
                if (activity.Tags.Count() <= 0) // If we didn't already load tags via the callback.
                    activity.AddTag("custom.prop", "1");
                activity.AddTag("custom.tag", Guid.NewGuid().ToString("n"));
            }
        }
CodeBlanch pushed a commit that referenced this issue Sep 11, 2023
CodeBlanch pushed a commit that referenced this issue Jul 16, 2024
* [wasm] Bump emscripten to 3.1.56

* Replace Module.asm with Module.wasmExports

Module.asm was removed, use wasmExports instead.
Context: emscripten-core/emscripten#19816

* Updates for .native.worker.js -> mjs rename

Context: emscripten-core/emscripten#21041

* Update deps

* Add general testing feed

* Update mode deps

* Update path

* Use current python packages for now, we don't have newer ones

The current names 3.1.34 are new enough

* Keep using llvm 16 for runtime and aot compiler

* Add -Wno-pre-c11-compat only for browser

* Temporarily disable version checks to get further

* Temporarily disable version checks to get further #2

* Disable -Wunused-command-line-argument

* Update emsdk deps

* Update icu dependency

* Revert "Temporarily disable version checks to get further #2"

This reverts commit 3f8834f.

* Revert "Temporarily disable version checks to get further"

This reverts commit fe1e5c6.

* Fix emsdk check

We use system python on osx too

* Workaround wasm-opt crash

* Workaround wasm-opt crash

* Workaround wasm-opt crash

* Fix WBT test

* Feedback

* Update ICU dependency

* Update emscripten deps

* Revert "Workaround wasm-opt crash"

This reverts commit 200cf3b.

* Revert "Workaround wasm-opt crash"

This reverts commit 4530edf.

* Revert "Workaround wasm-opt crash"

This reverts commit 3593c41.

* Increase tests timeout

* Show test progress

* Increase MT library tests timeout

* Disable WBT tests with SkiaSharp

* Increase helix tests timeout on browser

* Increase WBT timeout

* Increase initial heap sizes

* Fix mono_wasm_load_runtime cwrap signature

Fixes: `Uncaught ExitStatus: Assertion failed: stringToUTF8Array expects a string (got number)`

* Enable XunitShowProgress for threading tasks tests

* Try to reduce number of parallel AOT compilations

To check whether it will improve memory issues on CI

* Use new docker image for helix/windows tests

* Revert "Try to reduce number of parallel AOT compilations"

This reverts commit 5d9a6d2.

* Reduce the timeouts

* Reduce intitial heap size

* use active issues for MT

* Remove testing channel from nuget config, update deps

* Update emsdk and icu dependencies

---------

Co-authored-by: Larry Ewing <lewing@microsoft.com>
Co-authored-by: pavelsavara <pavel.savara@gmail.com>
CodeBlanch pushed a commit that referenced this issue Sep 9, 2024
* bug #1: don't allow for values out of the SerializationRecordType enum range

* bug #2: throw SerializationException rather than KeyNotFoundException when the referenced record is missing or it points to a record of different type

* bug #3: throw SerializationException rather than FormatException when it's being thrown by BinaryReader (or sth else that we use)

* bug #4: document the fact that IOException can be thrown

* bug #5: throw SerializationException rather than OverflowException when parsing the decimal fails

* bug #6: 0 and 17 are illegal values for PrimitiveType enum

* bug #7: throw SerializationException when a surrogate character is read (so far an ArgumentException was thrown)
CodeBlanch pushed a commit that referenced this issue Jan 10, 2025
* [NRBF] Don't use Unsafe.As when decoding DateTime(s) (dotnet#105749)

* Add NrbfDecoder Fuzzer (dotnet#107385)

* [NRBF] Fix bugs discovered by the fuzzer (dotnet#107368)

* bug #1: don't allow for values out of the SerializationRecordType enum range

* bug #2: throw SerializationException rather than KeyNotFoundException when the referenced record is missing or it points to a record of different type

* bug #3: throw SerializationException rather than FormatException when it's being thrown by BinaryReader (or sth else that we use)

* bug #4: document the fact that IOException can be thrown

* bug #5: throw SerializationException rather than OverflowException when parsing the decimal fails

* bug #6: 0 and 17 are illegal values for PrimitiveType enum

* bug #7: throw SerializationException when a surrogate character is read (so far an ArgumentException was thrown)
# Conflicts:
#	src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/NrbfDecoder.cs

* [NRBF] throw SerializationException when a surrogate character is read (dotnet#107532)

 (so far an ArgumentException was thrown)

* [NRBF] Fuzzing non-seekable stream input (dotnet#107605)

* [NRBF] More bug fixes (dotnet#107682)

- Don't use `Debug.Fail` not followed by an exception (it may cause problems for apps deployed in Debug)
- avoid Int32 overflow
- throw for unexpected enum values just in case parsing has not rejected them
- validate the number of chars read by BinaryReader.ReadChars
- pass serialization record id to ex message
- return false rather than throw EndOfStreamException when provided Stream has not enough data
- don't restore the position in finally 
- limit max SZ and MD array length to Array.MaxLength, stop using LinkedList<T> as List<T> will be able to hold all elements now
- remove internal enum values that were always illegal, but needed to be handled everywhere
- Fix DebuggerDisplay

* [NRBF] Comments and bug fixes from internal code review (dotnet#107735)

* copy comments and asserts from Levis internal code review

* apply Levis suggestion: don't store Array.MaxLength as a const, as it may change in the future

* add missing and fix some of the existing comments

* first bug fix: SerializationRecord.TypeNameMatches should throw ArgumentNullException for null Type argument

* second bug fix: SerializationRecord.TypeNameMatches should know the difference between SZArray and single-dimension, non-zero offset arrays (example: int[] and int[*])

* third bug fix: don't cast bytes to booleans

* fourth bug fix: don't cast bytes to DateTimes

* add one test case that I've forgot in previous PR
# Conflicts:
#	src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SerializationRecord.cs

* [NRBF] Address issues discovered by Threat Model  (dotnet#106629)

* introduce ArrayRecord.FlattenedLength

* do not include invalid Type or Assembly names in the exception messages, as it's most likely corrupted/tampered/malicious data and could be used as a vector of attack.

* It is possible to have binary array records have an element type of array without being marked as jagged

---------

Co-authored-by: Buyaa Namnan <bunamnan@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant