public void TestForcedSampling() { // Create tracer that samples no spans var tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .WithSampler(new ConstantSampler(false)) .Build(); var span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); Assert.NotNull(span.Context); bool?samplingDecision = ((WavefrontSpanContext)span.Context).GetSamplingDecision(); Assert.True(samplingDecision.HasValue); Assert.False(samplingDecision.Value); Tags.SamplingPriority.Set(span, 1); samplingDecision = ((WavefrontSpanContext)span.Context).GetSamplingDecision(); Assert.True(samplingDecision.HasValue); Assert.True(samplingDecision.Value); span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); Assert.NotNull(span.Context); samplingDecision = ((WavefrontSpanContext)span.Context).GetSamplingDecision(); Assert.True(samplingDecision.HasValue); Assert.False(samplingDecision.Value); Tags.Error.Set(span, true); samplingDecision = ((WavefrontSpanContext)span.Context).GetSamplingDecision(); Assert.True(samplingDecision.HasValue); Assert.True(samplingDecision.Value); }
public void TestNegativeChildSampling() { // Create tracer that samples all spans var tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .WithSampler(new ConstantSampler(true)) .Build(); // Create parentContext with sampled set to false var parentContext = new WavefrontSpanContext(Guid.NewGuid(), Guid.NewGuid(), null, false); // Verify span created AsChildOf parentContext inherits parent sampling decision var span = (WavefrontSpan)tracer.BuildSpan("testOp").AsChildOf(parentContext).Start(); var spanContext = (WavefrontSpanContext)span.Context; long traceId = TraceIdToLong(spanContext.GetTraceId()); Assert.True(tracer.Sample(span.GetOperationName(), traceId, 0)); Assert.NotNull(span); Assert.Equal(parentContext.TraceId, spanContext.TraceId); Assert.True(spanContext.IsSampled()); bool?samplingDecision = spanContext.GetSamplingDecision(); Assert.True(samplingDecision.HasValue); Assert.False(samplingDecision.Value); }
public void TestApplicationTags() { var customTags = new Dictionary <string, string> { { "customTag1", "customValue1" }, { "customTag2", "customValue2" } }; var applicationTags = new ApplicationTags.Builder("myApplication", "myServDefaultSource") .Cluster("myCluster") .Shard("myShard") .CustomTags(customTags) .Build(); var tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), applicationTags) .Build(); var span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); var spanTags = span.GetTagsAsMap(); Assert.NotNull(spanTags); Assert.Equal(6, spanTags.Count); Assert.Contains(applicationTags.Application, spanTags[ApplicationTagKey]); Assert.Contains(applicationTags.Service, spanTags[ServiceTagKey]); Assert.Contains(applicationTags.Cluster, spanTags[ClusterTagKey]); Assert.Contains(applicationTags.Shard, spanTags[ShardTagKey]); Assert.Contains("customValue1", spanTags["customTag1"]); Assert.Contains("customValue2", spanTags["customTag2"]); }
public async Task TestLateSpanFinish() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), BuildApplicationTags()) .Build(); // Create a Span manually and use it as parent of a pair of subtasks var parentSpan = tracer.BuildSpan("parent").Start(); var spans = new List <WavefrontSpan>(); using (var scope = tracer.ScopeManager.Activate(parentSpan, false)) { await SubmitTasks(tracer, spans); spans.Add((WavefrontSpan)parentSpan); } // Late-finish the parent Span now parentSpan.Finish(); Assert.Equal(3, spans.Count); Assert.Equal("task1", spans[0].GetOperationName()); Assert.Equal("task2", spans[1].GetOperationName()); Assert.Equal("parent", spans[2].GetOperationName()); AssertSameTrace(spans); Assert.Null(tracer.ActiveSpan); }
public void TestInjectExtract() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), BuildApplicationTags()) .Build(); var span = tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); span.SetBaggageItem("customer", "testCustomer"); span.SetBaggageItem("requestType", "mobile"); var dictionary = new Dictionary <string, string>(); var textMapInjectAdapter = new TextMapInjectAdapter(dictionary); tracer.Inject(span.Context, BuiltinFormats.TextMap, textMapInjectAdapter); var textMapExtractAdapter = new TextMapExtractAdapter(dictionary); var context = (WavefrontSpanContext)tracer.Extract(BuiltinFormats.TextMap, textMapExtractAdapter); Assert.Equal("testCustomer", context.GetBaggageItem("customer")); Assert.Equal("mobile", context.GetBaggageItem("requesttype")); }
public async Task TestActiveSpanReplacement() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), BuildApplicationTags()) .Build(); // Start an isolated task and query for its result in another task/thread ISpan initialSpan = tracer.BuildSpan("initial").Start(); // Explicitly pass a Span to be finished once a late calculation is done. var spans = await SubmitAnotherTask(tracer, initialSpan); Assert.Equal(3, spans.Count); Assert.Equal("initial", spans[0].GetOperationName()); // Isolated task Assert.Equal("subtask", spans[1].GetOperationName()); Assert.Equal("task", spans[2].GetOperationName()); var initialContext = (WavefrontSpanContext)spans[0].Context; var subtaskContext = (WavefrontSpanContext)spans[1].Context; var subtaskParentContext = spans[1].GetParents()[0].SpanContext; var taskContext = (WavefrontSpanContext)spans[2].Context; // task/subtask are part of the same trace, and subtask is a child of task Assert.Equal(subtaskContext.GetTraceId(), taskContext.GetTraceId()); Assert.Equal(taskContext.GetSpanId(), subtaskParentContext.GetSpanId()); // initial task is not related in any way to those two tasks Assert.NotEqual(initialContext.GetTraceId(), subtaskContext.GetTraceId()); Assert.Empty(spans[0].GetParents()); Assert.Null(tracer.ScopeManager.Active); }
public void TestGlobalTags() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), BuildApplicationTags()) .WithGlobalTag("foo", "bar") .Build(); var span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); var spanTags = span.GetTagsAsMap(); Assert.NotNull(spanTags); Assert.Equal(5, spanTags.Count); Assert.Contains("bar", spanTags["foo"]); var tags = new Dictionary <string, string> { { "foo1", "bar1" }, { "foo2", "bar2" } }; tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), BuildApplicationTags()) .WithGlobalTags(tags) .Build(); span = (WavefrontSpan)tracer.BuildSpan("testOp") .WithTag("foo3", "bar3") .Start(); Assert.NotNull(span); spanTags = span.GetTagsAsMap(); Assert.NotNull(spanTags); Assert.Equal(7, spanTags.Count); Assert.Contains("bar1", spanTags["foo1"]); Assert.Contains("bar2", spanTags["foo2"]); Assert.Contains("bar3", spanTags["foo3"]); }
public void TestBaggageItems() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .Build(); // Create parentContext with baggage items var bag = new Dictionary <string, string> { { "foo", "bar" }, { "user", "name" } }; var parentContext = new WavefrontSpanContext(Guid.NewGuid(), Guid.NewGuid(), bag, true); var span = (WavefrontSpan)tracer.BuildSpan("testOp").AsChildOf(parentContext).Start(); Assert.Equal("bar", span.GetBaggageItem("foo")); Assert.Equal("name", span.GetBaggageItem("user")); // Create follows var items = new Dictionary <string, string> { { "tracker", "id" }, { "db.name", "name" } }; var follows = new WavefrontSpanContext(Guid.NewGuid(), Guid.NewGuid(), items, true); span = (WavefrontSpan)tracer.BuildSpan("testOp") .AsChildOf(parentContext) .AsChildOf(follows) .Start(); Assert.Equal("bar", span.GetBaggageItem("foo")); Assert.Equal("name", span.GetBaggageItem("user")); Assert.Equal("id", span.GetBaggageItem("tracker")); Assert.Equal("name", span.GetBaggageItem("db.name")); // Validate root span span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); IDictionary <string, string> baggage = ((WavefrontSpanContext)span.Context).GetBaggage(); Assert.NotNull(baggage); Assert.Empty(baggage); }
public void TestActiveSpan() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), BuildApplicationTags()) .Build(); var scope = tracer.BuildSpan("testOp").StartActive(); var span = tracer.ActiveSpan; Assert.NotNull(span); Assert.Equal(span, scope.Span); }
public void TestIgnoreActiveSpan() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .Build(); var scope = tracer.BuildSpan("testOp").StartActive(true); var activeSpan = scope.Span; // Span created without invoking IgnoreActiveSpan() on WavefrontSpanBuilder var childSpan = tracer.BuildSpan("childOp").Start(); string activeTraceId = ((WavefrontSpanContext)activeSpan.Context).GetTraceId().ToString(); string childTraceId = ((WavefrontSpanContext)childSpan.Context).GetTraceId().ToString(); Assert.Equal(activeTraceId, childTraceId); // Span created with IgnoreActiveSpan() on WavefrontSpanBuilder childSpan = tracer.BuildSpan("childOp").IgnoreActiveSpan().Start(); childTraceId = ((WavefrontSpanContext)childSpan.Context).GetTraceId().ToString(); Assert.NotEqual(activeTraceId, childTraceId); }
public void TestGlobalMultiValuedTags() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter(DefaultSource), BuildApplicationTags()) .WithGlobalTag("key1", "value1") .WithGlobalTag("key1", "value2") .Build(); var span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); var spanTags = span.GetTagsAsMap(); Assert.NotNull(spanTags); Assert.Equal(5, spanTags.Count); Assert.Contains("value1", spanTags["key1"]); Assert.Contains("value2", spanTags["key1"]); }
public void TestSpanLogs() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .Build(); var span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); var dateTime1 = DateTimeOffset.UtcNow; var timestamp1 = DateTimeUtils.UnixTimeMicroseconds(dateTime1.UtcDateTime); span.Log(dateTime1, "event1"); var dateTime2 = DateTimeOffset.UtcNow; var timestamp2 = DateTimeUtils.UnixTimeMicroseconds(dateTime2.UtcDateTime); span.Log(dateTime2, new Dictionary <string, object> { { "event", "event2" }, { "event.kind", "error" } }); var spanLogs = span.GetSpanLogs(); Assert.Equal(2, spanLogs.Count); SpanLog log1 = spanLogs[0]; Assert.Equal(timestamp1, log1.TimestampMicros); Assert.Equal(new Dictionary <string, string> { { LogFields.Event, "event1" } }, log1.Fields); SpanLog log2 = spanLogs[1]; Assert.Equal(timestamp2, log2.TimestampMicros); Assert.Equal(new Dictionary <string, string> { { "event", "event2" }, { "event.kind", "error" } }, log2.Fields); }
public void TestMultiValuedTags() { var tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .Build(); var span = (WavefrontSpan)tracer.BuildSpan("testOp") .WithTag("key1", "value1") .WithTag("key1", "value2") .WithTag(ApplicationTagKey, "yourApplication") .Start(); Assert.NotNull(span); var spanTags = span.GetTagsAsMap(); Assert.NotNull(spanTags); Assert.Equal(5, spanTags.Count); Assert.Contains("value1", spanTags["key1"]); Assert.Contains("value2", spanTags["key1"]); Assert.Contains("myService", spanTags[ServiceTagKey]); // Check that application tag was replaced Assert.Equal(1, spanTags[ApplicationTagKey].Count); Assert.Contains("yourApplication", spanTags[ApplicationTagKey]); Assert.Equal("yourApplication", span.GetSingleValuedTagValue(ApplicationTagKey)); }
public void TestRootSampling() { // Create tracer that samples no spans var tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .WithSampler(new ConstantSampler(false)) .Build(); var span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); Assert.NotNull(span.Context); Assert.Empty(span.GetParents()); Assert.Empty(span.GetFollows()); Assert.True(((WavefrontSpanContext)span.Context).IsSampled()); bool?samplingDecision = ((WavefrontSpanContext)span.Context).GetSamplingDecision(); Assert.True(samplingDecision.HasValue); Assert.False(samplingDecision.Value); // Create tracer that samples all spans tracer = new WavefrontTracer .Builder(new ConsoleReporter("source"), BuildApplicationTags()) .WithSampler(new ConstantSampler(true)) .Build(); span = (WavefrontSpan)tracer.BuildSpan("testOp").Start(); Assert.NotNull(span); Assert.NotNull(span.Context); Assert.Empty(span.GetParents()); Assert.Empty(span.GetFollows()); Assert.True(((WavefrontSpanContext)span.Context).IsSampled()); samplingDecision = ((WavefrontSpanContext)span.Context).GetSamplingDecision(); Assert.True(samplingDecision.HasValue); Assert.True(samplingDecision.Value); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { //Begin code instrumentation - reference https://github.com/wavefrontHQ/wavefront-opentracing-sdk-csharp //The application, service, cluster, and shard variables are all metadata to be added to each span created. string application = "VMworld2020Demo"; string service = "GlobalDataAggregator"; string cluster = "Azure"; string shard = "networknerd4"; //The URL and token are for direct ingestion of metrics, traces, and spans (no proxy in use here). //The API token can be found inside the Tanzu Observability (Wavefront) web UI and is unique to your environment. Click the gear icon in the upper right, click your e-mail address, and then select API Access. string wfURL = "https://vmware.wavefront.com"; string token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; // Create ApplicationTags - for tracing purposes ApplicationTags applicationTags = new ApplicationTags.Builder(application, service).Cluster(cluster).Shard(shard).Build(); //Configure a MetricsBuilder object - for custom metrics sent via the Metrics SDK var MyMetricsBuilder = new MetricsBuilder(); //Initialize WavefrontDirectIngestionClient WavefrontDirectIngestionClient.Builder wfDirectIngestionClientBuilder = new WavefrontDirectIngestionClient.Builder(wfURL, token); // Create an IWavefrontSender instance for sending data via direct ingestion. IWavefrontSender wavefrontSender = wfDirectIngestionClientBuilder.Build(); //Configure MeetricsBuilder to Report to Wavefront with proper sender object and source tag specified. In this case my source is the function name. MyMetricsBuilder.Report.ToWavefront( options => { options.WavefrontSender = wavefrontSender; options.Source = "TruckGlobalDataAggregator"; }); //Build IMetrics instance var MyMetrics = MyMetricsBuilder.Build(); //These are arrays for key value pairs to add as metric tags. You can add some or many here as you instrument your code. string[] keys = new string[3] { "FunctionApp", "Cloud", "Region" }; string[] values = new string[3] { "networknerd4", "Azure", "Central-US" }; // Configure and instantiate a DeltaCounter using DeltaCounterOptions.Builder. The metric name is azure.function.execution.deltacounter. var myDeltaCounter = new DeltaCounterOptions.Builder("azure.function.execution.deltacounter").MeasurementUnit(Unit.Calls).Tags(new MetricTags(keys, values)).Build(); // Increment the counter by 1 MyMetrics.Measure.Counter.Increment(myDeltaCounter); //Force reporting all custom metrics await Task.WhenAll(MyMetrics.ReportRunner.RunAllAsync()); //Create a WavefrontSpanReporter for reporting trace data that originates on <sourceName>. The source is the function name in this case. IReporter wfSpanReporter = new WavefrontSpanReporter.Builder() .WithSource("TruckGlobalDataAggregator").Build(wavefrontSender); //Create CompositeReporter and ConsoleReporter objects for more OpenTracing metrics IReporter consoleReporter = new ConsoleReporter("TruckGlobalDataAggregator"); IReporter compositeReporter = new CompositeReporter(wfSpanReporter, consoleReporter); //Create the WavefrontTracer. WavefrontTracer MyTracer = new WavefrontTracer.Builder(wfSpanReporter, applicationTags).Build(); //The variable MyDictionary is needed to extract span context in case a call is made from another function / outside this function. IDictionary <string, string> MyDictionary = new Dictionary <string, string>(); foreach (var entry in req.Headers) { MyDictionary.TryAdd(entry.Key, entry.Value); } //Attempt to pull span fontext from HTTP headers passed into this function to continue a span across environments. The proper context will be loaded into the variable //ctx if so. The second line of code loads all metadata from the span context. ITextMap carrier = new TextMapExtractAdapter(MyDictionary); OpenTracing.ISpanContext ctx = MyTracer.Extract(BuiltinFormats.HttpHeaders, carrier); OpenTracing.IScope receivingScope = MyTracer.BuildSpan("TruckGlobalDataAggregator.Execute").AsChildOf(ctx).StartActive(true); //Start building a new span called TruckGlobalDataAggregator.Execute if there was no context passed into headers. if (MyTracer.ActiveSpan != null) { MyTracer.BuildSpan("TruckGlobalDataAggregator.Execute").StartActive(); } log.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); name = name ?? data?.name; //Add function execution delays based on input - for personal testing only. if (string.Equals(name, "0.5")) { await Task.Delay(500); } if (string.Equals(name, "1")) { await Task.Delay(1000); } if (string.Equals(name, "1.5")) { await Task.Delay(1500); } if (string.Equals(name, "2")) { await Task.Delay(2000); } string responseMessage = string.IsNullOrEmpty(name) ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." : $"Hello, {name}. This HTTP triggered function executed successfully."; //Finish the span MyTracer.ActiveSpan.Finish(); //Close the tracer before application exit MyTracer.Close(); return(new OkObjectResult(responseMessage)); }
public void TestDebugWavefrontSpan() { string operationName = "dummyOp"; var wfSenderMock = new Mock <IWavefrontSender>(MockBehavior.Strict); wfSenderMock.Reset(); Expression <Action <IWavefrontSender> > sendSpan = sender => sender.SendSpan(operationName, IsAny <long>(), IsAny <long>(), "source", IsAny <Guid>(), IsAny <Guid>(), new List <Guid>(), new List <Guid>(), IsAny <IList <KeyValuePair <string, string> > >(), new List <SpanLog>()); Expression <Action <IWavefrontSender> > sendInvocationCount = sender => sender.SendMetric( "∆tracing.derived.myApplication.myService.dummyOp.invocation.count", 1.0, null, "source", IsAny <IDictionary <string, string> >()); Expression <Action <IWavefrontSender> > sendTotalMillis = sender => sender.SendMetric( "∆tracing.derived.myApplication.myService.dummyOp.total_time.millis.count", IsAny <double>(), null, "source", IsAny <IDictionary <string, string> >()); Expression <Action <IWavefrontSender> > sendDurationMicros = sender => sender.SendDistribution( "tracing.derived.myApplication.myService.dummyOp.duration.micros", IsAny <IList <KeyValuePair <double, int> > >(), new HashSet <HistogramGranularity> { HistogramGranularity.Minute }, IsAny <long>(), "source", IsAny <IDictionary <string, string> >()); Expression <Action <IWavefrontSender> > sendHeartbeat = sender => sender.SendMetric( "~component.heartbeat", 1.0, IsAny <long>(), "source", IsAny <IDictionary <string, string> >()); wfSenderMock.Setup(sendSpan); wfSenderMock.Setup(sendInvocationCount); wfSenderMock.Setup(sendTotalMillis); wfSenderMock.Setup(sendDurationMicros); wfSenderMock.Setup(sendHeartbeat); WavefrontSpanReporter spanReporter = new WavefrontSpanReporter.Builder() .WithSource("source").Build(wfSenderMock.Object); WavefrontTracer tracer = new WavefrontTracer.Builder(spanReporter, BuildApplicationTags()).SetReportFrequency(TimeSpan.FromMilliseconds(50)) .WithSampler(new RateSampler(0.0)).Build(); tracer.BuildSpan(operationName).WithTag("debug", true) .StartActive(true).Span.Finish(DateTimeOffset.Now.AddMilliseconds(10)); Console.WriteLine("Sleeping for 1 second zzzzz ....."); Thread.Sleep(1000); Console.WriteLine("Resuming execution ....."); wfSenderMock.Verify(sendSpan, Times.Once()); wfSenderMock.Verify(sendInvocationCount, Times.AtLeastOnce()); wfSenderMock.Verify(sendTotalMillis, Times.AtLeastOnce()); /* * TODO: update WavefrontHistogramOptions.Builder to allow a clock to be passed in * so that we can advance minute bin and update the below call to Times.AtLeastOnce() */ wfSenderMock.Verify(sendDurationMicros, Times.AtMost(int.MaxValue)); wfSenderMock.Verify(sendHeartbeat, Times.AtMost(int.MaxValue)); }
public void TestErrorWavefrontSpan() { string operationName = "dummyOp"; var pointTags = PointTags(operationName, new Dictionary <string, string> { { Tags.SpanKind.Key, Constants.NullTagValue } }); var errorTags = PointTags(operationName, new Dictionary <string, string> { { Tags.SpanKind.Key, Constants.NullTagValue }, { Tags.HttpStatus.Key, "404" } }); var histogramTags = PointTags(operationName, new Dictionary <string, string> { { Tags.SpanKind.Key, Constants.NullTagValue }, { "error", "true" } }); var wfSenderMock = new Mock <IWavefrontSender>(MockBehavior.Strict); Expression <Action <IWavefrontSender> > sendSpan = sender => sender.SendSpan(operationName, IsAny <long>(), IsAny <long>(), "source", IsAny <Guid>(), IsAny <Guid>(), new List <Guid>(), new List <Guid>(), IsAny <IList <KeyValuePair <string, string> > >(), new List <SpanLog>()); Expression <Action <IWavefrontSender> > sendInvocationCount = sender => sender.SendMetric( "tracing.derived.myApplication.myService.dummyOp.invocation.count", 1.0, IsAny <long>(), "source", Is <IDictionary <string, string> >(dict => ContainsPointTags(dict, pointTags))); Expression <Action <IWavefrontSender> > sendErrorCount = sender => sender.SendMetric( "tracing.derived.myApplication.myService.dummyOp.error.count", 1.0, IsAny <long>(), "source", Is <IDictionary <string, string> >(dict => ContainsPointTags(dict, errorTags))); Expression <Action <IWavefrontSender> > sendTotalMillis = sender => sender.SendMetric( "tracing.derived.myApplication.myService.dummyOp.total_time.millis.count", IsAny <double>(), IsAny <long>(), "source", Is <IDictionary <string, string> >(dict => ContainsPointTags(dict, pointTags))); Expression <Action <IWavefrontSender> > sendDurationMicros = sender => sender.SendDistribution( "tracing.derived.myApplication.myService.dummyOp.duration.micros", IsAny <IList <KeyValuePair <double, int> > >(), new HashSet <HistogramGranularity> { HistogramGranularity.Minute }, IsAny <long>(), "source", Is <IDictionary <string, string> >(dict => ContainsPointTags(dict, histogramTags))); Expression <Action <IWavefrontSender> > sendHeartbeat = sender => sender.SendMetric( "~component.heartbeat", 1.0, IsAny <long>(), "source", IsAny <IDictionary <string, string> >()); wfSenderMock.Setup(sendSpan); wfSenderMock.Setup(sendInvocationCount); wfSenderMock.Setup(sendErrorCount); wfSenderMock.Setup(sendTotalMillis); wfSenderMock.Setup(sendDurationMicros); wfSenderMock.Setup(sendHeartbeat); WavefrontSpanReporter spanReporter = new WavefrontSpanReporter.Builder() .WithSource("source").Build(wfSenderMock.Object); WavefrontTracer tracer = new WavefrontTracer.Builder(spanReporter, BuildApplicationTags()).SetReportFrequency(TimeSpan.FromMilliseconds(50)).Build(); tracer.BuildSpan(operationName).WithTag(Tags.Error, true).WithTag(Tags.HttpStatus, 404) .StartActive(true).Dispose(); Console.WriteLine("Sleeping for 1 second zzzzz ....."); Thread.Sleep(1000); Console.WriteLine("Resuming execution ....."); wfSenderMock.Verify(sendSpan, Times.Once()); wfSenderMock.Verify(sendInvocationCount, Times.AtLeastOnce()); wfSenderMock.Verify(sendErrorCount, Times.AtLeastOnce()); wfSenderMock.Verify(sendTotalMillis, Times.AtLeastOnce()); /* * TODO: update WavefrontHistogramOptions.Builder to allow a clock to be passed in * so that we can advance minute bin and update the below call to Times.AtLeastOnce() */ wfSenderMock.Verify(sendDurationMicros, Times.AtMost(int.MaxValue)); wfSenderMock.Verify(sendHeartbeat, Times.AtMost(int.MaxValue)); }