From 0c01f9f0aa46816cc913f562307a6668bb65a5fb Mon Sep 17 00:00:00 2001 From: Sergey Kanzhelev Date: Wed, 8 May 2019 12:32:06 -0700 Subject: [PATCH] copy of OpenCensus at 0474607a16282252697f989113d68bdf71959070 --- .gitignore | 330 ++ .vscode/launch.json | 28 + .vscode/settings.json | 6 + .vscode/tasks.json | 17 + .vsts/ci-build.yml | 60 + .vsts/ci-myget-update.yml | 51 + CHANGELOG.md | 40 + CONTRIBUTING.md | 50 + LICENSE | 201 + NuGet.config | 8 + OpenCensus.proj | 19 + OpenCensus.sln | 158 + README.md | 316 +- build/Common.prod.props | 62 + build/Common.test.props | 18 + build/OpenCensus.prod.loose.ruleset | 172 + build/OpenCensus.prod.ruleset | 172 + build/OpenCensus.test.ruleset | 173 + build/debug.snk | Bin 0 -> 596 bytes build/stylecop.json | 15 + docs/error-handling.md | 41 + docs/error-logging.md | 103 + docs/event-source-listeners.png | Bin 0 -> 18244 bytes packages-microsoft-prod.deb | Bin 0 -> 2452 bytes .../Common/Duration.cs | 137 + src/OpenCensus.Abstractions/Common/IScope.cs | 28 + src/OpenCensus.Abstractions/Common/Timer.cs | 81 + .../Common/Timestamp.cs | 229 + .../Internal/IEventQueue.cs | 30 + .../Internal/IEventQueueEntry.cs | 29 + .../OpenCensus.Abstractions.csproj | 35 + .../Properties/AssemblyInfo.cs | 36 + .../Resources/IResource.cs | 40 + .../Stats/Aggregations/ICount.cs | 25 + .../Stats/Aggregations/ICountData.cs | 29 + .../Stats/Aggregations/IDistribution.cs | 29 + .../Stats/Aggregations/IDistributionData.cs | 56 + .../Stats/Aggregations/ILastValue.cs | 25 + .../Aggregations/ILastValueDataDouble.cs | 29 + .../Stats/Aggregations/ILastValueDataLong.cs | 29 + .../Stats/Aggregations/IMean.cs | 25 + .../Stats/Aggregations/IMeanData.cs | 44 + .../Stats/Aggregations/ISum.cs | 25 + .../Stats/Aggregations/ISumDataDouble.cs | 29 + .../Stats/Aggregations/ISumDataLong.cs | 29 + .../Stats/IAggregation.cs | 46 + .../Stats/IAggregationData.cs | 50 + .../Stats/IBucketBoundaries.cs | 31 + src/OpenCensus.Abstractions/Stats/IMeasure.cs | 55 + .../Stats/IMeasureMap.cs | 54 + .../Stats/IMeasurement.cs | 42 + .../Stats/IStatsComponent.cs | 39 + .../Stats/IStatsRecorder.cs | 30 + src/OpenCensus.Abstractions/Stats/IView.cs | 52 + .../Stats/IViewData.cs | 48 + .../Stats/IViewManager.cs | 44 + .../Stats/IViewName.cs | 29 + .../Stats/Measurements/IMeasurementDouble.cs | 29 + .../Stats/Measurements/IMeasurementLong.cs | 29 + .../Stats/Measures/IMeasureDouble.cs | 25 + .../Stats/Measures/IMeasureLong.cs | 25 + .../Stats/StatsCollectionState.cs | 34 + src/OpenCensus.Abstractions/Tags/ITag.cs | 34 + .../Tags/ITagContext.cs | 27 + .../Tags/ITagContextBuilder.cs | 53 + src/OpenCensus.Abstractions/Tags/ITagKey.cs | 29 + src/OpenCensus.Abstractions/Tags/ITagValue.cs | 29 + src/OpenCensus.Abstractions/Tags/ITagger.cs | 62 + .../Tags/ITagsComponent.cs | 41 + .../ITagContextBinarySerializer.cs | 38 + .../Propagation/ITagPropagationComponent.cs | 29 + src/OpenCensus.Abstractions/Tags/TagValues.cs | 110 + .../Tags/TaggingState.cs | 34 + .../Trace/Annotation.cs | 93 + .../Trace/AttributeValue.cs | 83 + .../Trace/CanonicalCode.cs | 145 + .../Trace/Config/ITraceConfig.cs | 35 + .../Trace/Config/ITraceParams.cs | 55 + .../Trace/Config/TraceParams.cs | 136 + .../Trace/Config/TraceParamsBuilder.cs | 141 + .../Trace/EndSpanOptions.cs | 95 + .../Trace/EndSpanOptionsBuilder.cs | 77 + .../Trace/Export/Attributes.cs | 84 + .../Trace/Export/IAttributes.cs | 36 + .../Trace/Export/IExportComponent.cs | 39 + .../Trace/Export/IHandler.cs | 34 + .../Trace/Export/ILinks.cs | 36 + .../Export/IRunningPerSpanNameSummary.cs | 29 + .../Trace/Export/IRunningSpanStore.cs | 50 + .../Trace/Export/IRunningSpanStoreFilter.cs | 34 + .../Trace/Export/IRunningSpanStoreSummary.cs | 31 + .../Export/ISampledLatencyBucketBoundaries.cs | 36 + .../Export/ISampledPerSpanNameSummary.cs | 36 + .../Trace/Export/ISampledSpanStore.cs | 68 + .../Export/ISampledSpanStoreErrorFilter.cs | 39 + .../Export/ISampledSpanStoreLatencyFilter.cs | 46 + .../Trace/Export/ISampledSpanStoreSummary.cs | 31 + .../Trace/Export/ISpanData.cs | 91 + .../Trace/Export/ISpanExporter.cs | 58 + .../Trace/Export/ITimedEvent.cs | 37 + .../Trace/Export/ITimedEvents.cs | 37 + .../Trace/Export/LinkList.cs | 84 + .../Trace/Export/SpanData.cs | 204 + .../Trace/Export/TimedEvent.cs | 75 + .../Trace/Export/TimedEvents.cs | 82 + .../Trace/IAnnotation.cs | 36 + .../Trace/IAttributeValue.cs | 63 + src/OpenCensus.Abstractions/Trace/ILink.cs | 46 + .../Trace/IMessageEvent.cs | 44 + .../Trace/IRandomGenerator.cs | 23 + src/OpenCensus.Abstractions/Trace/ISampler.cs | 47 + src/OpenCensus.Abstractions/Trace/ISpan.cs | 115 + .../Trace/ISpanBuilder.cs | 70 + .../Trace/ISpanContext.cs | 49 + src/OpenCensus.Abstractions/Trace/ISpanId.cs | 49 + .../Trace/IStartEndHandler.cs | 36 + .../Trace/ITraceComponent.cs | 49 + src/OpenCensus.Abstractions/Trace/ITraceId.cs | 54 + src/OpenCensus.Abstractions/Trace/ITracer.cs | 64 + .../Trace/Internal/AttributeValue{T}.cs | 151 + src/OpenCensus.Abstractions/Trace/LinkType.cs | 39 + .../Trace/MessageEventType.cs | 39 + .../Trace/Propagation/IBinaryFormat.cs | 38 + .../Propagation/IPropagationComponent.cs | 34 + .../Trace/Propagation/ITextFormat.cs | 53 + .../Trace/Sampler/AlwaysSampleSampler.cs | 46 + .../Trace/Sampler/NeverSampleSampler.cs | 46 + .../Trace/Sampler/ProbabilitySampler.cs | 139 + .../Trace/Sampler/Samplers.cs | 59 + .../Trace/SpanAttributeConstants.cs | 31 + .../Trace/SpanContext.cs | 90 + .../Trace/SpanExtensions.cs | 224 + src/OpenCensus.Abstractions/Trace/SpanId.cs | 163 + src/OpenCensus.Abstractions/Trace/SpanKind.cs | 39 + .../Trace/SpanOptions.cs | 37 + src/OpenCensus.Abstractions/Trace/Status.cs | 222 + src/OpenCensus.Abstractions/Trace/TraceId.cs | 188 + .../Trace/TraceOptions.cs | 200 + .../Trace/TraceOptionsBuilder.cs | 64 + .../Trace/TraceState.cs | 346 ++ src/OpenCensus.Abstractions/Utils/Arrays.cs | 145 + .../Utils/CanonicalCodeExtensions.cs | 28 + .../Utils/Collections.cs | 71 + .../Utils/DoubleUtil.cs | 56 + src/OpenCensus.Abstractions/Utils/IElement.cs | 36 + .../AspNetCoreCollectorEventSource.cs | 74 + .../AssemblyInfo.cs | 17 + .../Implementation/HttpInListener.cs | 178 + .../DiagnosticSourceListener.cs | 83 + .../DiagnosticSourceSubscriber.cs | 96 + .../ListenerHandler.cs | 69 + .../PropertyFetcher.cs | 94 + .../OpenCensus.Collector.AspNetCore.csproj | 29 + .../RequestsCollector.cs | 74 + .../RequestsCollectorOptions.cs | 46 + .../AssemblyInfo.cs | 17 + .../DependenciesCollector.cs | 70 + .../DependenciesCollectorEventSource.cs | 74 + .../DependenciesCollectorOptions.cs | 46 + .../HttpHandlerDiagnosticListener.cs | 135 + .../DiagnosticSourceListener.cs | 83 + .../DiagnosticSourceSubscriber.cs | 96 + .../ListenerHandler.cs | 69 + .../PropertyFetcher.cs | 94 + .../OpenCensus.Collector.Dependencies.csproj | 35 + .../AssemblyInfo.cs | 37 + .../RedisProfilerEntryToSpanConverter.cs | 154 + ...Census.Collector.StackExchangeRedis.csproj | 25 + .../StackExchangeRedisCallsCollector.cs | 133 + ...StackExchangeRedisCallsCollectorOptions.cs | 25 + .../ApplicationInsightsExporter.cs | 108 + .../Implementation/MetricsExporterThread.cs | 195 + .../Implementation/TraceExporterHandler.cs | 515 +++ ...Census.Exporter.ApplicationInsights.csproj | 31 + .../Properties/AssemblyInfo.cs | 36 + .../README.md | 27 + .../AssemblyInfo.cs | 15 + .../ExporterOcagentEventSource.cs | 63 + .../Implementation/SpanDataExtentions.cs | 182 + .../Implementation/TraceExporterHandler.cs | 179 + .../Implementation/gen/Common.g.cs | 864 ++++ .../Implementation/gen/Resource.g.cs | 207 + .../Implementation/gen/Trace.g.cs | 4106 +++++++++++++++++ .../Implementation/gen/TraceConfig.g.cs | 837 ++++ .../Implementation/gen/TraceService.g.cs | 728 +++ .../Implementation/gen/TraceServiceGrpc.cs | 180 + .../OcagentExporter.cs | 100 + .../OpenCensus.Exporter.Ocagent.csproj | 28 + .../AssemblyInfo.cs | 17 + .../Implementation/MetricsHttpServer.cs | 118 + .../Implementation/PrometheusMetricBuilder.cs | 385 ++ .../OpenCensus.Exporter.Prometheus.csproj | 28 + .../PrometheusExporter.cs | 90 + .../PrometheusExporterOptions.cs | 31 + .../AssemblyInfo.cs | 34 + .../Implementation/Constants.cs | 54 + .../ExporterStackdriverEventSource.cs | 78 + .../GoogleCloudResourceUtils.cs | 77 + .../Implementation/MetricsConversions.cs | 336 ++ .../StackdriverStatsConfiguration.cs | 77 + .../StackdriverStatsExporter.cs | 368 ++ .../StackdriverTraceExporter.cs | 192 + .../OpenCensus.Exporter.Stackdriver.csproj | 31 + .../StackdriverExporter.cs | 160 + .../Utils/CommonUtils.cs | 55 + .../Utils/ProtoExtensions.cs | 37 + .../AssemblyInfo.cs | 19 + .../Implementation/TraceExporterHandler.cs | 297 ++ .../Implementation/ZipkinAnnotation.cs | 29 + .../Implementation/ZipkinEndpoint.cs | 35 + .../Implementation/ZipkinSpan.cs | 186 + .../Implementation/ZipkinSpanKind.cs | 41 + .../OpenCensus.Exporter.Zipkin.csproj | 31 + .../ZipkinTraceExporter.cs | 95 + .../ZipkinTraceExporterOptions.cs | 47 + src/OpenCensus/Implementation/Constants.cs | 44 + .../Implementation/OpenCensusEventSource.cs | 84 + src/OpenCensus/Internal/NoopScope.cs | 41 + src/OpenCensus/Internal/SimpleEventQueue.cs | 26 + src/OpenCensus/Internal/VarInt.cs | 277 ++ src/OpenCensus/OpenCensus.csproj | 39 + src/OpenCensus/Properties/AssemblyInfo.cs | 39 + src/OpenCensus/Resources/Resource.cs | 187 + src/OpenCensus/Stats/Aggregation.cs | 26 + src/OpenCensus/Stats/AggregationData.cs | 34 + src/OpenCensus/Stats/Aggregations/Count.cs | 69 + .../Stats/Aggregations/CountData.cs | 81 + .../Stats/Aggregations/Distribution.cs | 82 + .../Stats/Aggregations/DistributionData.cs | 135 + .../Stats/Aggregations/LastValue.cs | 69 + .../Stats/Aggregations/LastValueDataDouble.cs | 86 + .../Stats/Aggregations/LastValueDataLong.cs | 85 + src/OpenCensus/Stats/Aggregations/Mean.cs | 69 + src/OpenCensus/Stats/Aggregations/MeanData.cs | 103 + src/OpenCensus/Stats/Aggregations/Sum.cs | 69 + .../Stats/Aggregations/SumDataDouble.cs | 82 + .../Stats/Aggregations/SumDataLong.cs | 81 + src/OpenCensus/Stats/BucketBoundaries.cs | 93 + .../Stats/CumulativeMutableViewData.cs | 77 + src/OpenCensus/Stats/CurrentStatsState.cs | 74 + src/OpenCensus/Stats/Measure.cs | 34 + src/OpenCensus/Stats/MeasureMap.cs | 60 + src/OpenCensus/Stats/MeasureMapBase.cs | 32 + src/OpenCensus/Stats/MeasureMapBuilder.cs | 64 + src/OpenCensus/Stats/MeasureToViewMap.cs | 222 + src/OpenCensus/Stats/Measurement.cs | 28 + .../Stats/Measurements/MeasurementDouble.cs | 82 + .../Stats/Measurements/MeasurementLong.cs | 81 + .../Stats/Measures/MeasureDouble.cs | 96 + src/OpenCensus/Stats/Measures/MeasureLong.cs | 96 + src/OpenCensus/Stats/MutableAggregation.cs | 36 + src/OpenCensus/Stats/MutableCount.cs | 56 + src/OpenCensus/Stats/MutableDistribution.cs | 156 + src/OpenCensus/Stats/MutableLastValue.cs | 67 + src/OpenCensus/Stats/MutableMean.cs | 92 + src/OpenCensus/Stats/MutableSum.cs | 54 + src/OpenCensus/Stats/MutableViewData.cs | 215 + src/OpenCensus/Stats/NoopMeasureMap.cs | 49 + src/OpenCensus/Stats/NoopStats.cs | 51 + src/OpenCensus/Stats/NoopStatsComponent.cs | 53 + src/OpenCensus/Stats/NoopStatsRecorder.cs | 28 + src/OpenCensus/Stats/NoopViewManager.cs | 116 + src/OpenCensus/Stats/Stats.cs | 66 + src/OpenCensus/Stats/StatsComponent.cs | 80 + src/OpenCensus/Stats/StatsComponentBase.cs | 27 + src/OpenCensus/Stats/StatsExtensions.cs | 235 + src/OpenCensus/Stats/StatsManager.cs | 95 + src/OpenCensus/Stats/StatsRecorder.cs | 35 + src/OpenCensus/Stats/StatsRecorderBase.cs | 23 + src/OpenCensus/Stats/View.cs | 111 + src/OpenCensus/Stats/ViewData.cs | 205 + src/OpenCensus/Stats/ViewManager.cs | 58 + src/OpenCensus/Stats/ViewManagerBase.cs | 29 + src/OpenCensus/Stats/ViewName.cs | 84 + src/OpenCensus/Tags/CurrentTagContextUtils.cs | 56 + src/OpenCensus/Tags/CurrentTaggingState.cs | 64 + src/OpenCensus/Tags/NoopTagContext.cs | 30 + .../Tags/NoopTagContextBinarySerializer.cs | 47 + src/OpenCensus/Tags/NoopTagContextBuilder.cs | 66 + .../Tags/NoopTagPropagationComponent.cs | 33 + src/OpenCensus/Tags/NoopTagger.cs | 79 + src/OpenCensus/Tags/NoopTags.cs | 68 + src/OpenCensus/Tags/NoopTagsComponent.cs | 49 + .../Tags/Propagation/SerializationUtils.cs | 190 + .../Propagation/TagContextBinarySerializer.cs | 44 + .../TagContextBinarySerializerBase.cs | 25 + .../TagContextDeserializationException.cs | 33 + .../TagContextSerializationException.cs | 33 + .../Propagation/TagPropagationComponent.cs | 33 + .../TagPropagationComponentBase.cs | 27 + src/OpenCensus/Tags/Tag.cs | 75 + src/OpenCensus/Tags/TagContext.cs | 40 + src/OpenCensus/Tags/TagContextBase.cs | 88 + src/OpenCensus/Tags/TagContextBuilder.cs | 73 + src/OpenCensus/Tags/TagContextBuilderBase.cs | 31 + src/OpenCensus/Tags/TagKey.cs | 81 + src/OpenCensus/Tags/TagValue.cs | 81 + src/OpenCensus/Tags/Tagger.cs | 123 + src/OpenCensus/Tags/TaggerBase.cs | 35 + src/OpenCensus/Tags/Tags.cs | 84 + src/OpenCensus/Tags/TagsComponent.cs | 50 + src/OpenCensus/Tags/TagsComponentBase.cs | 29 + .../Tags/Unsafe/AsyncLocalContext.cs | 61 + .../Trace/Config/NoopTraceConfig.cs | 30 + src/OpenCensus/Trace/Config/TraceConfig.cs | 41 + .../Trace/Config/TraceConfigBase.cs | 35 + src/OpenCensus/Trace/CurrentSpanUtils.cs | 70 + src/OpenCensus/Trace/EventWithTime.cs | 40 + .../Trace/Export/ExportComponent.cs | 58 + .../Trace/Export/ExportComponentBase.cs | 35 + .../Trace/Export/InProcessRunningSpanStore.cs | 93 + .../Trace/Export/InProcessSampledSpanStore.cs | 433 ++ .../Trace/Export/LatencyBucketBoundaries.cs | 49 + .../Trace/Export/NoopExportComponent.cs | 47 + .../Trace/Export/NoopRunningSpanStore.cs | 52 + .../Trace/Export/NoopSampledSpanStore.cs | 104 + .../Trace/Export/NoopSpanExporter.cs | 46 + .../Trace/Export/RunningPerSpanNameSummary.cs | 73 + .../Trace/Export/RunningSpanStoreBase.cs | 45 + .../Trace/Export/RunningSpanStoreFilter.cs | 80 + .../Trace/Export/RunningSpanStoreSummary.cs | 77 + .../Trace/Export/SampledPerSpanNameSummary.cs | 90 + .../Trace/Export/SampledSpanStoreBase.cs | 59 + .../Export/SampledSpanStoreErrorFilter.cs | 92 + .../Export/SampledSpanStoreLatencyFilter.cs | 104 + .../Trace/Export/SampledSpanStoreSummary.cs | 77 + src/OpenCensus/Trace/Export/SpanExporter.cs | 83 + .../Trace/Export/SpanExporterBase.cs | 46 + .../Trace/Export/SpanExporterWorker.cs | 151 + src/OpenCensus/Trace/Export/SpanExtensions.cs | 22 + src/OpenCensus/Trace/Internal/BlankSpan.cs | 112 + .../Trace/Internal/RandomGenerator.cs | 57 + .../Trace/Internal/StartEndHandler.cs | 117 + src/OpenCensus/Trace/Link.cs | 105 + src/OpenCensus/Trace/MessageEvent.cs | 94 + src/OpenCensus/Trace/MessageEventBuilder.cs | 103 + src/OpenCensus/Trace/NoopSpanBuilder.cs | 63 + src/OpenCensus/Trace/NoopTraceComponent.cs | 59 + src/OpenCensus/Trace/NoopTracer.cs | 35 + src/OpenCensus/Trace/Propagation/B3Format.cs | 139 + .../Trace/Propagation/BinaryFormatBase.cs | 37 + .../DefaultPropagationComponent.cs | 47 + .../Implementation/BinaryFormat.cs | 101 + .../Implementation/NoopBinaryFormat.cs | 43 + .../NoopPropagationComponent.cs | 37 + .../Implementation/NoopTextFormat.cs | 63 + .../Propagation/PropagationComponentBase.cs | 37 + .../Propagation/SpanContextParseException.cs | 33 + .../Trace/Propagation/TextFormatBase.cs | 48 + .../Trace/Propagation/TraceContextFormat.cs | 300 ++ src/OpenCensus/Trace/Span.cs | 482 ++ src/OpenCensus/Trace/SpanBase.cs | 151 + src/OpenCensus/Trace/SpanBuilder.cs | 235 + src/OpenCensus/Trace/SpanBuilderBase.cs | 54 + src/OpenCensus/Trace/SpanBuilderOptions.cs | 36 + src/OpenCensus/Trace/TraceComponent.cs | 79 + src/OpenCensus/Trace/TraceEvents.cs | 50 + src/OpenCensus/Trace/Tracer.cs | 40 + src/OpenCensus/Trace/TracerBase.cs | 65 + src/OpenCensus/Trace/Tracing.cs | 66 + src/OpenCensus/Utils/Arrays.cs | 145 + .../Utils/AttributesWithCapacity.cs | 193 + src/OpenCensus/Utils/Collections.cs | 71 + .../Utils/ConcurrentIntrusiveList.cs | 115 + src/OpenCensus/Utils/DefaultEventQueue.cs | 29 + src/OpenCensus/Utils/DoubleUtil.cs | 56 + src/OpenCensus/Utils/EvictingQueue.cs | 113 + src/OpenCensus/Utils/StringUtil.cs | 39 + src/Samples/Program.cs | 71 + src/Samples/Samples.csproj | 23 + src/Samples/TestApplicationInsights.cs | 73 + src/Samples/TestHttpClient.cs | 45 + src/Samples/TestPrometheus.cs | 79 + src/Samples/TestRedis.cs | 104 + src/Samples/TestStackdriver.cs | 76 + src/Samples/TestZipkin.cs | 84 + .../BasicTests.cs | 154 + ...stsCollectionsIsAccordingToTheSpecTests.cs | 104 + ...enCensus.Collector.AspNetCore.Tests.csproj | 54 + .../xunit.runner.json | 4 + .../BasicTests.cs | 91 + .../HttpClientTests.cs | 220 + ...Census.Collector.Dependencies.Tests.csproj | 57 + .../TestServer.cs | 119 + .../http-out-test-cases.json | 274 ++ .../xunit.runner.json | 4 + ...ofilerEntryToSpanConverterSamplingTests.cs | 84 + .../RedisProfilerEntryToSpanConverterTests.cs | 85 + ....Collector.StackExchangeRedis.Tests.csproj | 36 + .../StackExchangeRedisCallsCollectorTests.cs | 50 + .../Implementation/StubTelemetryChannel.cs | 96 + .../TraceExporterHandlerTests.cs | 1873 ++++++++ ....Exporter.ApplicationInsights.Tests.csproj | 44 + .../xunit.runner.json | 3 + .../StackdriverStatsConfigurationTests.cs | 68 + ...enCensus.Exporter.Stackdriver.Tests.csproj | 50 + .../xunit.runner.json | 4 + .../Impl/Common/DurationTest.cs | 76 + .../Impl/Common/TimestampTest.cs | 161 + .../Impl/Internal/TimestampConverterTest.cs | 29 + .../Impl/Resources/ResourceTest.cs | 173 + .../Impl/Stats/AggregationDataTest.cs | 182 + .../Impl/Stats/AggregationTest.cs | 114 + .../Impl/Stats/BucketBoundariesTest.cs | 83 + .../Impl/Stats/CurrentStatsStateTest.cs | 104 + .../Impl/Stats/MeasureMapBuilderTest.cs | 153 + .../Impl/Stats/MeasureTest.cs | 119 + .../Impl/Stats/MeasureToViewMapTest.cs | 52 + .../Impl/Stats/MutableAggregationTest.cs | 256 + .../Impl/Stats/MutableViewDataTest.cs | 127 + .../Impl/Stats/NoopStatsTest.cs | 111 + .../Impl/Stats/NoopViewManagerTest.cs | 174 + .../Impl/Stats/QuickStartExampleTest.cs | 224 + .../Impl/Stats/StatsComponentTest.cs | 65 + .../Impl/Stats/StatsDefaultTest.cs | 69 + .../Impl/Stats/StatsRecorderTest.cs | 238 + test/OpenCensus.Tests/Impl/Stats/StatsTest.cs | 35 + .../Impl/Stats/StatsTestUtil.cs | 175 + .../Impl/Stats/ViewDataTest.cs | 295 ++ .../Impl/Stats/ViewManagerTest.cs | 781 ++++ test/OpenCensus.Tests/Impl/Stats/ViewTest.cs | 131 + .../Impl/Tags/CurrentTagContextUtilsTest.cs | 112 + .../Impl/Tags/CurrentTaggingStateTest.cs | 49 + .../Impl/Tags/NoopTagsTest.cs | 146 + .../TagContextBinarySerializerTest.cs | 88 + .../TagContextDeserializationExceptionTest.cs | 39 + .../TagContextDeserializationTest.cs | 332 ++ .../Propagation/TagContextRoundtripTest.cs | 90 + .../TagContextSerializationExceptionTest.cs | 39 + .../TagContextSerializationTest.cs | 168 + .../Impl/Tags/ScopedTagContextsTest.cs | 118 + .../Impl/Tags/TagContextBaseTest.cs | 118 + .../Impl/Tags/TagContextTest.cs | 154 + test/OpenCensus.Tests/Impl/Tags/TagKeyTest.cs | 86 + test/OpenCensus.Tests/Impl/Tags/TagTest.cs | 45 + .../Impl/Tags/TagValueTest.cs | 82 + test/OpenCensus.Tests/Impl/Tags/TaggerTest.cs | 350 ++ .../Impl/Tags/TagsComponentBaseTest.cs | 31 + .../Impl/Tags/TagsDefaultTest.cs | 49 + test/OpenCensus.Tests/Impl/Tags/TagsTest.cs | 41 + .../Impl/Tags/TagsTestUtil.cs | 29 + .../Impl/Testing/Export/TestHandler.cs | 66 + .../Impl/Trace/AnnotationTest.cs | 105 + .../Impl/Trace/AttributeValueTest.cs | 81 + .../Impl/Trace/BlankSpanTest.cs | 62 + .../Impl/Trace/Config/TraceConfigBaseTest.cs | 48 + .../Impl/Trace/Config/TraceConfigTest.cs | 50 + .../Impl/Trace/Config/TraceParamsTest.cs | 88 + .../Impl/Trace/CurrentSpanUtilsTest.cs | 85 + .../Impl/Trace/EndSpanOptionsTest.cs | 80 + .../Trace/Export/ExportComponentBaseTest.cs | 43 + .../Impl/Trace/Export/ExportComponentTest.cs | 47 + .../Export/InProcessRunningSpanStoreTest.cs | 146 + .../Export/InProcessSampledSpanStoreTest.cs | 385 ++ .../Trace/Export/NoopRunningSpanStoreTest.cs | 47 + .../Trace/Export/NoopSampledSpanStoreTest.cs | 93 + .../Impl/Trace/Export/SpanDataTest.cs | 299 ++ .../Impl/Trace/Export/SpanExporterTest.cs | 218 + .../Internal/ConcurrentIntrusiveListTest.cs | 112 + test/OpenCensus.Tests/Impl/Trace/LinkTest.cs | 114 + .../Impl/Trace/MessageEventTest.cs | 82 + test/OpenCensus.Tests/Impl/Trace/NoopSpan.cs | 75 + .../Impl/Trace/Propagation/B3FormatTest.cs | 217 + .../Trace/Propagation/BinaryFormatBaseTest.cs | 51 + .../Trace/Propagation/BinaryFormatTest.cs | 156 + .../PropagationComponentBaseTest.cs | 32 + .../Propagation/PropagationComponentTest.cs | 38 + .../SpanContextParseExceptionTest.cs | 39 + .../Trace/Propagation/TextFormatBaseTest.cs | 50 + .../Trace/Propagation/TraceContextTest.cs | 56 + .../Impl/Trace/Sampler/SamplersTest.cs | 288 ++ .../Impl/Trace/SpanBaseTest.cs | 107 + .../Impl/Trace/SpanBuilderBaseTest.cs | 71 + .../Impl/Trace/SpanBuilderTest.cs | 356 ++ .../Impl/Trace/SpanContextTest.cs | 122 + .../OpenCensus.Tests/Impl/Trace/SpanIdTest.cs | 93 + test/OpenCensus.Tests/Impl/Trace/SpanTest.cs | 582 +++ .../OpenCensus.Tests/Impl/Trace/StatusTest.cs | 52 + .../Impl/Trace/TraceComponentTest.cs | 48 + .../Impl/Trace/TraceIdTest.cs | 96 + .../Impl/Trace/TraceOptionsTest.cs | 82 + .../Impl/Trace/TracerBaseTest.cs | 183 + .../OpenCensus.Tests/Impl/Trace/TracerTest.cs | 53 + .../Impl/Trace/TracingTest.cs | 108 + test/OpenCensus.Tests/OpenCensus.Tests.csproj | 50 + test/OpenCensus.Tests/xunit.runner.json | 4 + .../CallbackMiddleware.cs | 50 + .../Controllers/ForwardController.cs | 83 + .../Controllers/ValuesController.cs | 57 + test/TestApp.AspNetCore.2.0/Program.cs | 33 + test/TestApp.AspNetCore.2.0/Startup.cs | 86 + .../TestApp.AspNetCore.2.0.csproj | 34 + .../appsettings.Development.json | 10 + test/TestApp.AspNetCore.2.0/appsettings.json | 15 + 494 files changed, 51020 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 .vsts/ci-build.yml create mode 100644 .vsts/ci-myget-update.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 NuGet.config create mode 100644 OpenCensus.proj create mode 100644 OpenCensus.sln create mode 100644 build/Common.prod.props create mode 100644 build/Common.test.props create mode 100644 build/OpenCensus.prod.loose.ruleset create mode 100644 build/OpenCensus.prod.ruleset create mode 100644 build/OpenCensus.test.ruleset create mode 100644 build/debug.snk create mode 100644 build/stylecop.json create mode 100644 docs/error-handling.md create mode 100644 docs/error-logging.md create mode 100644 docs/event-source-listeners.png create mode 100644 packages-microsoft-prod.deb create mode 100644 src/OpenCensus.Abstractions/Common/Duration.cs create mode 100644 src/OpenCensus.Abstractions/Common/IScope.cs create mode 100644 src/OpenCensus.Abstractions/Common/Timer.cs create mode 100644 src/OpenCensus.Abstractions/Common/Timestamp.cs create mode 100644 src/OpenCensus.Abstractions/Internal/IEventQueue.cs create mode 100644 src/OpenCensus.Abstractions/Internal/IEventQueueEntry.cs create mode 100644 src/OpenCensus.Abstractions/OpenCensus.Abstractions.csproj create mode 100644 src/OpenCensus.Abstractions/Properties/AssemblyInfo.cs create mode 100644 src/OpenCensus.Abstractions/Resources/IResource.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ICount.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ICountData.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/IDistribution.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/IDistributionData.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ILastValue.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataDouble.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataLong.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/IMean.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/IMeanData.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ISum.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataDouble.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataLong.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IAggregation.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IAggregationData.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IBucketBoundaries.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IMeasure.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IMeasureMap.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IMeasurement.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IStatsComponent.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IStatsRecorder.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IView.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IViewData.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IViewManager.cs create mode 100644 src/OpenCensus.Abstractions/Stats/IViewName.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementDouble.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementLong.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Measures/IMeasureDouble.cs create mode 100644 src/OpenCensus.Abstractions/Stats/Measures/IMeasureLong.cs create mode 100644 src/OpenCensus.Abstractions/Stats/StatsCollectionState.cs create mode 100644 src/OpenCensus.Abstractions/Tags/ITag.cs create mode 100644 src/OpenCensus.Abstractions/Tags/ITagContext.cs create mode 100644 src/OpenCensus.Abstractions/Tags/ITagContextBuilder.cs create mode 100644 src/OpenCensus.Abstractions/Tags/ITagKey.cs create mode 100644 src/OpenCensus.Abstractions/Tags/ITagValue.cs create mode 100644 src/OpenCensus.Abstractions/Tags/ITagger.cs create mode 100644 src/OpenCensus.Abstractions/Tags/ITagsComponent.cs create mode 100644 src/OpenCensus.Abstractions/Tags/Propagation/ITagContextBinarySerializer.cs create mode 100644 src/OpenCensus.Abstractions/Tags/Propagation/ITagPropagationComponent.cs create mode 100644 src/OpenCensus.Abstractions/Tags/TagValues.cs create mode 100644 src/OpenCensus.Abstractions/Tags/TaggingState.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Annotation.cs create mode 100644 src/OpenCensus.Abstractions/Trace/AttributeValue.cs create mode 100644 src/OpenCensus.Abstractions/Trace/CanonicalCode.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Config/ITraceConfig.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Config/ITraceParams.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Config/TraceParams.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Config/TraceParamsBuilder.cs create mode 100644 src/OpenCensus.Abstractions/Trace/EndSpanOptions.cs create mode 100644 src/OpenCensus.Abstractions/Trace/EndSpanOptionsBuilder.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/Attributes.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/IAttributes.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/IExportComponent.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/IHandler.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ILinks.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/IRunningPerSpanNameSummary.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStore.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreFilter.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreSummary.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISampledLatencyBucketBoundaries.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISampledPerSpanNameSummary.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStore.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreErrorFilter.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreLatencyFilter.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreSummary.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISpanData.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ISpanExporter.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ITimedEvent.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/ITimedEvents.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/LinkList.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/SpanData.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/TimedEvent.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Export/TimedEvents.cs create mode 100644 src/OpenCensus.Abstractions/Trace/IAnnotation.cs create mode 100644 src/OpenCensus.Abstractions/Trace/IAttributeValue.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ILink.cs create mode 100644 src/OpenCensus.Abstractions/Trace/IMessageEvent.cs create mode 100644 src/OpenCensus.Abstractions/Trace/IRandomGenerator.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ISampler.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ISpan.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ISpanBuilder.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ISpanContext.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ISpanId.cs create mode 100644 src/OpenCensus.Abstractions/Trace/IStartEndHandler.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ITraceComponent.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ITraceId.cs create mode 100644 src/OpenCensus.Abstractions/Trace/ITracer.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Internal/AttributeValue{T}.cs create mode 100644 src/OpenCensus.Abstractions/Trace/LinkType.cs create mode 100644 src/OpenCensus.Abstractions/Trace/MessageEventType.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Propagation/IBinaryFormat.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Propagation/IPropagationComponent.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Propagation/ITextFormat.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Sampler/AlwaysSampleSampler.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Sampler/NeverSampleSampler.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Sampler/ProbabilitySampler.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Sampler/Samplers.cs create mode 100644 src/OpenCensus.Abstractions/Trace/SpanAttributeConstants.cs create mode 100644 src/OpenCensus.Abstractions/Trace/SpanContext.cs create mode 100644 src/OpenCensus.Abstractions/Trace/SpanExtensions.cs create mode 100644 src/OpenCensus.Abstractions/Trace/SpanId.cs create mode 100644 src/OpenCensus.Abstractions/Trace/SpanKind.cs create mode 100644 src/OpenCensus.Abstractions/Trace/SpanOptions.cs create mode 100644 src/OpenCensus.Abstractions/Trace/Status.cs create mode 100644 src/OpenCensus.Abstractions/Trace/TraceId.cs create mode 100644 src/OpenCensus.Abstractions/Trace/TraceOptions.cs create mode 100644 src/OpenCensus.Abstractions/Trace/TraceOptionsBuilder.cs create mode 100644 src/OpenCensus.Abstractions/Trace/TraceState.cs create mode 100644 src/OpenCensus.Abstractions/Utils/Arrays.cs create mode 100644 src/OpenCensus.Abstractions/Utils/CanonicalCodeExtensions.cs create mode 100644 src/OpenCensus.Abstractions/Utils/Collections.cs create mode 100644 src/OpenCensus.Abstractions/Utils/DoubleUtil.cs create mode 100644 src/OpenCensus.Abstractions/Utils/IElement.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/AspNetCoreCollectorEventSource.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/AssemblyInfo.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/Implementation/HttpInListener.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceListener.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceSubscriber.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/ListenerHandler.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/PropertyFetcher.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/OpenCensus.Collector.AspNetCore.csproj create mode 100644 src/OpenCensus.Collector.AspNetCore/RequestsCollector.cs create mode 100644 src/OpenCensus.Collector.AspNetCore/RequestsCollectorOptions.cs create mode 100644 src/OpenCensus.Collector.Dependencies/AssemblyInfo.cs create mode 100644 src/OpenCensus.Collector.Dependencies/DependenciesCollector.cs create mode 100644 src/OpenCensus.Collector.Dependencies/DependenciesCollectorEventSource.cs create mode 100644 src/OpenCensus.Collector.Dependencies/DependenciesCollectorOptions.cs create mode 100644 src/OpenCensus.Collector.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs create mode 100644 src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceListener.cs create mode 100644 src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceSubscriber.cs create mode 100644 src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/ListenerHandler.cs create mode 100644 src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/PropertyFetcher.cs create mode 100644 src/OpenCensus.Collector.Dependencies/OpenCensus.Collector.Dependencies.csproj create mode 100644 src/OpenCensus.Collector.StackExchangeRedis/AssemblyInfo.cs create mode 100644 src/OpenCensus.Collector.StackExchangeRedis/Implementation/RedisProfilerEntryToSpanConverter.cs create mode 100644 src/OpenCensus.Collector.StackExchangeRedis/OpenCensus.Collector.StackExchangeRedis.csproj create mode 100644 src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollector.cs create mode 100644 src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollectorOptions.cs create mode 100644 src/OpenCensus.Exporter.ApplicationInsights/ApplicationInsightsExporter.cs create mode 100644 src/OpenCensus.Exporter.ApplicationInsights/Implementation/MetricsExporterThread.cs create mode 100644 src/OpenCensus.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs create mode 100644 src/OpenCensus.Exporter.ApplicationInsights/OpenCensus.Exporter.ApplicationInsights.csproj create mode 100644 src/OpenCensus.Exporter.ApplicationInsights/Properties/AssemblyInfo.cs create mode 100644 src/OpenCensus.Exporter.ApplicationInsights/README.md create mode 100644 src/OpenCensus.Exporter.Ocagent/AssemblyInfo.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/ExporterOcagentEventSource.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/SpanDataExtentions.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/TraceExporterHandler.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/gen/Common.g.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/gen/Resource.g.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/gen/Trace.g.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceConfig.g.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceService.g.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceServiceGrpc.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/OcagentExporter.cs create mode 100644 src/OpenCensus.Exporter.Ocagent/OpenCensus.Exporter.Ocagent.csproj create mode 100644 src/OpenCensus.Exporter.Prometheus/AssemblyInfo.cs create mode 100644 src/OpenCensus.Exporter.Prometheus/Implementation/MetricsHttpServer.cs create mode 100644 src/OpenCensus.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs create mode 100644 src/OpenCensus.Exporter.Prometheus/OpenCensus.Exporter.Prometheus.csproj create mode 100644 src/OpenCensus.Exporter.Prometheus/PrometheusExporter.cs create mode 100644 src/OpenCensus.Exporter.Prometheus/PrometheusExporterOptions.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/AssemblyInfo.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Implementation/Constants.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Implementation/ExporterStackdriverEventSource.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Implementation/GoogleCloudResourceUtils.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Implementation/MetricsConversions.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsConfiguration.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsExporter.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverTraceExporter.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/OpenCensus.Exporter.Stackdriver.csproj create mode 100644 src/OpenCensus.Exporter.Stackdriver/StackdriverExporter.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Utils/CommonUtils.cs create mode 100644 src/OpenCensus.Exporter.Stackdriver/Utils/ProtoExtensions.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/AssemblyInfo.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/Implementation/TraceExporterHandler.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinAnnotation.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpan.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpanKind.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/OpenCensus.Exporter.Zipkin.csproj create mode 100644 src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporter.cs create mode 100644 src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporterOptions.cs create mode 100644 src/OpenCensus/Implementation/Constants.cs create mode 100644 src/OpenCensus/Implementation/OpenCensusEventSource.cs create mode 100644 src/OpenCensus/Internal/NoopScope.cs create mode 100644 src/OpenCensus/Internal/SimpleEventQueue.cs create mode 100644 src/OpenCensus/Internal/VarInt.cs create mode 100644 src/OpenCensus/OpenCensus.csproj create mode 100644 src/OpenCensus/Properties/AssemblyInfo.cs create mode 100644 src/OpenCensus/Resources/Resource.cs create mode 100644 src/OpenCensus/Stats/Aggregation.cs create mode 100644 src/OpenCensus/Stats/AggregationData.cs create mode 100644 src/OpenCensus/Stats/Aggregations/Count.cs create mode 100644 src/OpenCensus/Stats/Aggregations/CountData.cs create mode 100644 src/OpenCensus/Stats/Aggregations/Distribution.cs create mode 100644 src/OpenCensus/Stats/Aggregations/DistributionData.cs create mode 100644 src/OpenCensus/Stats/Aggregations/LastValue.cs create mode 100644 src/OpenCensus/Stats/Aggregations/LastValueDataDouble.cs create mode 100644 src/OpenCensus/Stats/Aggregations/LastValueDataLong.cs create mode 100644 src/OpenCensus/Stats/Aggregations/Mean.cs create mode 100644 src/OpenCensus/Stats/Aggregations/MeanData.cs create mode 100644 src/OpenCensus/Stats/Aggregations/Sum.cs create mode 100644 src/OpenCensus/Stats/Aggregations/SumDataDouble.cs create mode 100644 src/OpenCensus/Stats/Aggregations/SumDataLong.cs create mode 100644 src/OpenCensus/Stats/BucketBoundaries.cs create mode 100644 src/OpenCensus/Stats/CumulativeMutableViewData.cs create mode 100644 src/OpenCensus/Stats/CurrentStatsState.cs create mode 100644 src/OpenCensus/Stats/Measure.cs create mode 100644 src/OpenCensus/Stats/MeasureMap.cs create mode 100644 src/OpenCensus/Stats/MeasureMapBase.cs create mode 100644 src/OpenCensus/Stats/MeasureMapBuilder.cs create mode 100644 src/OpenCensus/Stats/MeasureToViewMap.cs create mode 100644 src/OpenCensus/Stats/Measurement.cs create mode 100644 src/OpenCensus/Stats/Measurements/MeasurementDouble.cs create mode 100644 src/OpenCensus/Stats/Measurements/MeasurementLong.cs create mode 100644 src/OpenCensus/Stats/Measures/MeasureDouble.cs create mode 100644 src/OpenCensus/Stats/Measures/MeasureLong.cs create mode 100644 src/OpenCensus/Stats/MutableAggregation.cs create mode 100644 src/OpenCensus/Stats/MutableCount.cs create mode 100644 src/OpenCensus/Stats/MutableDistribution.cs create mode 100644 src/OpenCensus/Stats/MutableLastValue.cs create mode 100644 src/OpenCensus/Stats/MutableMean.cs create mode 100644 src/OpenCensus/Stats/MutableSum.cs create mode 100644 src/OpenCensus/Stats/MutableViewData.cs create mode 100644 src/OpenCensus/Stats/NoopMeasureMap.cs create mode 100644 src/OpenCensus/Stats/NoopStats.cs create mode 100644 src/OpenCensus/Stats/NoopStatsComponent.cs create mode 100644 src/OpenCensus/Stats/NoopStatsRecorder.cs create mode 100644 src/OpenCensus/Stats/NoopViewManager.cs create mode 100644 src/OpenCensus/Stats/Stats.cs create mode 100644 src/OpenCensus/Stats/StatsComponent.cs create mode 100644 src/OpenCensus/Stats/StatsComponentBase.cs create mode 100644 src/OpenCensus/Stats/StatsExtensions.cs create mode 100644 src/OpenCensus/Stats/StatsManager.cs create mode 100644 src/OpenCensus/Stats/StatsRecorder.cs create mode 100644 src/OpenCensus/Stats/StatsRecorderBase.cs create mode 100644 src/OpenCensus/Stats/View.cs create mode 100644 src/OpenCensus/Stats/ViewData.cs create mode 100644 src/OpenCensus/Stats/ViewManager.cs create mode 100644 src/OpenCensus/Stats/ViewManagerBase.cs create mode 100644 src/OpenCensus/Stats/ViewName.cs create mode 100644 src/OpenCensus/Tags/CurrentTagContextUtils.cs create mode 100644 src/OpenCensus/Tags/CurrentTaggingState.cs create mode 100644 src/OpenCensus/Tags/NoopTagContext.cs create mode 100644 src/OpenCensus/Tags/NoopTagContextBinarySerializer.cs create mode 100644 src/OpenCensus/Tags/NoopTagContextBuilder.cs create mode 100644 src/OpenCensus/Tags/NoopTagPropagationComponent.cs create mode 100644 src/OpenCensus/Tags/NoopTagger.cs create mode 100644 src/OpenCensus/Tags/NoopTags.cs create mode 100644 src/OpenCensus/Tags/NoopTagsComponent.cs create mode 100644 src/OpenCensus/Tags/Propagation/SerializationUtils.cs create mode 100644 src/OpenCensus/Tags/Propagation/TagContextBinarySerializer.cs create mode 100644 src/OpenCensus/Tags/Propagation/TagContextBinarySerializerBase.cs create mode 100644 src/OpenCensus/Tags/Propagation/TagContextDeserializationException.cs create mode 100644 src/OpenCensus/Tags/Propagation/TagContextSerializationException.cs create mode 100644 src/OpenCensus/Tags/Propagation/TagPropagationComponent.cs create mode 100644 src/OpenCensus/Tags/Propagation/TagPropagationComponentBase.cs create mode 100644 src/OpenCensus/Tags/Tag.cs create mode 100644 src/OpenCensus/Tags/TagContext.cs create mode 100644 src/OpenCensus/Tags/TagContextBase.cs create mode 100644 src/OpenCensus/Tags/TagContextBuilder.cs create mode 100644 src/OpenCensus/Tags/TagContextBuilderBase.cs create mode 100644 src/OpenCensus/Tags/TagKey.cs create mode 100644 src/OpenCensus/Tags/TagValue.cs create mode 100644 src/OpenCensus/Tags/Tagger.cs create mode 100644 src/OpenCensus/Tags/TaggerBase.cs create mode 100644 src/OpenCensus/Tags/Tags.cs create mode 100644 src/OpenCensus/Tags/TagsComponent.cs create mode 100644 src/OpenCensus/Tags/TagsComponentBase.cs create mode 100644 src/OpenCensus/Tags/Unsafe/AsyncLocalContext.cs create mode 100644 src/OpenCensus/Trace/Config/NoopTraceConfig.cs create mode 100644 src/OpenCensus/Trace/Config/TraceConfig.cs create mode 100644 src/OpenCensus/Trace/Config/TraceConfigBase.cs create mode 100644 src/OpenCensus/Trace/CurrentSpanUtils.cs create mode 100644 src/OpenCensus/Trace/EventWithTime.cs create mode 100644 src/OpenCensus/Trace/Export/ExportComponent.cs create mode 100644 src/OpenCensus/Trace/Export/ExportComponentBase.cs create mode 100644 src/OpenCensus/Trace/Export/InProcessRunningSpanStore.cs create mode 100644 src/OpenCensus/Trace/Export/InProcessSampledSpanStore.cs create mode 100644 src/OpenCensus/Trace/Export/LatencyBucketBoundaries.cs create mode 100644 src/OpenCensus/Trace/Export/NoopExportComponent.cs create mode 100644 src/OpenCensus/Trace/Export/NoopRunningSpanStore.cs create mode 100644 src/OpenCensus/Trace/Export/NoopSampledSpanStore.cs create mode 100644 src/OpenCensus/Trace/Export/NoopSpanExporter.cs create mode 100644 src/OpenCensus/Trace/Export/RunningPerSpanNameSummary.cs create mode 100644 src/OpenCensus/Trace/Export/RunningSpanStoreBase.cs create mode 100644 src/OpenCensus/Trace/Export/RunningSpanStoreFilter.cs create mode 100644 src/OpenCensus/Trace/Export/RunningSpanStoreSummary.cs create mode 100644 src/OpenCensus/Trace/Export/SampledPerSpanNameSummary.cs create mode 100644 src/OpenCensus/Trace/Export/SampledSpanStoreBase.cs create mode 100644 src/OpenCensus/Trace/Export/SampledSpanStoreErrorFilter.cs create mode 100644 src/OpenCensus/Trace/Export/SampledSpanStoreLatencyFilter.cs create mode 100644 src/OpenCensus/Trace/Export/SampledSpanStoreSummary.cs create mode 100644 src/OpenCensus/Trace/Export/SpanExporter.cs create mode 100644 src/OpenCensus/Trace/Export/SpanExporterBase.cs create mode 100644 src/OpenCensus/Trace/Export/SpanExporterWorker.cs create mode 100644 src/OpenCensus/Trace/Export/SpanExtensions.cs create mode 100644 src/OpenCensus/Trace/Internal/BlankSpan.cs create mode 100644 src/OpenCensus/Trace/Internal/RandomGenerator.cs create mode 100644 src/OpenCensus/Trace/Internal/StartEndHandler.cs create mode 100644 src/OpenCensus/Trace/Link.cs create mode 100644 src/OpenCensus/Trace/MessageEvent.cs create mode 100644 src/OpenCensus/Trace/MessageEventBuilder.cs create mode 100644 src/OpenCensus/Trace/NoopSpanBuilder.cs create mode 100644 src/OpenCensus/Trace/NoopTraceComponent.cs create mode 100644 src/OpenCensus/Trace/NoopTracer.cs create mode 100644 src/OpenCensus/Trace/Propagation/B3Format.cs create mode 100644 src/OpenCensus/Trace/Propagation/BinaryFormatBase.cs create mode 100644 src/OpenCensus/Trace/Propagation/DefaultPropagationComponent.cs create mode 100644 src/OpenCensus/Trace/Propagation/Implementation/BinaryFormat.cs create mode 100644 src/OpenCensus/Trace/Propagation/Implementation/NoopBinaryFormat.cs create mode 100644 src/OpenCensus/Trace/Propagation/Implementation/NoopPropagationComponent.cs create mode 100644 src/OpenCensus/Trace/Propagation/Implementation/NoopTextFormat.cs create mode 100644 src/OpenCensus/Trace/Propagation/PropagationComponentBase.cs create mode 100644 src/OpenCensus/Trace/Propagation/SpanContextParseException.cs create mode 100644 src/OpenCensus/Trace/Propagation/TextFormatBase.cs create mode 100644 src/OpenCensus/Trace/Propagation/TraceContextFormat.cs create mode 100644 src/OpenCensus/Trace/Span.cs create mode 100644 src/OpenCensus/Trace/SpanBase.cs create mode 100644 src/OpenCensus/Trace/SpanBuilder.cs create mode 100644 src/OpenCensus/Trace/SpanBuilderBase.cs create mode 100644 src/OpenCensus/Trace/SpanBuilderOptions.cs create mode 100644 src/OpenCensus/Trace/TraceComponent.cs create mode 100644 src/OpenCensus/Trace/TraceEvents.cs create mode 100644 src/OpenCensus/Trace/Tracer.cs create mode 100644 src/OpenCensus/Trace/TracerBase.cs create mode 100644 src/OpenCensus/Trace/Tracing.cs create mode 100644 src/OpenCensus/Utils/Arrays.cs create mode 100644 src/OpenCensus/Utils/AttributesWithCapacity.cs create mode 100644 src/OpenCensus/Utils/Collections.cs create mode 100644 src/OpenCensus/Utils/ConcurrentIntrusiveList.cs create mode 100644 src/OpenCensus/Utils/DefaultEventQueue.cs create mode 100644 src/OpenCensus/Utils/DoubleUtil.cs create mode 100644 src/OpenCensus/Utils/EvictingQueue.cs create mode 100644 src/OpenCensus/Utils/StringUtil.cs create mode 100644 src/Samples/Program.cs create mode 100644 src/Samples/Samples.csproj create mode 100644 src/Samples/TestApplicationInsights.cs create mode 100644 src/Samples/TestHttpClient.cs create mode 100644 src/Samples/TestPrometheus.cs create mode 100644 src/Samples/TestRedis.cs create mode 100644 src/Samples/TestStackdriver.cs create mode 100644 src/Samples/TestZipkin.cs create mode 100644 test/OpenCensus.Collector.AspNetCore.Tests/BasicTests.cs create mode 100644 test/OpenCensus.Collector.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs create mode 100644 test/OpenCensus.Collector.AspNetCore.Tests/OpenCensus.Collector.AspNetCore.Tests.csproj create mode 100644 test/OpenCensus.Collector.AspNetCore.Tests/xunit.runner.json create mode 100644 test/OpenCensus.Collector.Dependencies.Tests/BasicTests.cs create mode 100644 test/OpenCensus.Collector.Dependencies.Tests/HttpClientTests.cs create mode 100644 test/OpenCensus.Collector.Dependencies.Tests/OpenCensus.Collector.Dependencies.Tests.csproj create mode 100644 test/OpenCensus.Collector.Dependencies.Tests/TestServer.cs create mode 100644 test/OpenCensus.Collector.Dependencies.Tests/http-out-test-cases.json create mode 100644 test/OpenCensus.Collector.Dependencies.Tests/xunit.runner.json create mode 100644 test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterSamplingTests.cs create mode 100644 test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterTests.cs create mode 100644 test/OpenCensus.Collector.StackExchangeRedis.Tests/OpenCensus.Collector.StackExchangeRedis.Tests.csproj create mode 100644 test/OpenCensus.Collector.StackExchangeRedis.Tests/StackExchangeRedisCallsCollectorTests.cs create mode 100644 test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/StubTelemetryChannel.cs create mode 100644 test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs create mode 100644 test/OpenCensus.Exporter.ApplicationInsights.Tests/OpenCensus.Exporter.ApplicationInsights.Tests.csproj create mode 100644 test/OpenCensus.Exporter.ApplicationInsights.Tests/xunit.runner.json create mode 100644 test/OpenCensus.Exporter.Stackdriver.Tests/Impl/StackdriverStatsConfigurationTests.cs create mode 100644 test/OpenCensus.Exporter.Stackdriver.Tests/OpenCensus.Exporter.Stackdriver.Tests.csproj create mode 100644 test/OpenCensus.Exporter.Stackdriver.Tests/xunit.runner.json create mode 100644 test/OpenCensus.Tests/Impl/Common/DurationTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Common/TimestampTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Internal/TimestampConverterTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Resources/ResourceTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/AggregationDataTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/AggregationTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/BucketBoundariesTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/CurrentStatsStateTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/MeasureMapBuilderTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/MeasureTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/MeasureToViewMapTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/MutableAggregationTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/MutableViewDataTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/NoopStatsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/NoopViewManagerTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/QuickStartExampleTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/StatsComponentTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/StatsDefaultTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/StatsRecorderTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/StatsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/StatsTestUtil.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/ViewDataTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/ViewManagerTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Stats/ViewTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/CurrentTagContextUtilsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/CurrentTaggingStateTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/NoopTagsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextBinarySerializerTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationExceptionTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextRoundtripTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationExceptionTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/ScopedTagContextsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagContextBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagContextTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagKeyTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagValueTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TaggerTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagsComponentBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagsDefaultTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Tags/TagsTestUtil.cs create mode 100644 test/OpenCensus.Tests/Impl/Testing/Export/TestHandler.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/AnnotationTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/AttributeValueTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/BlankSpanTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Config/TraceParamsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/CurrentSpanUtilsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/EndSpanOptionsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/InProcessRunningSpanStoreTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/InProcessSampledSpanStoreTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/NoopRunningSpanStoreTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/NoopSampledSpanStoreTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/SpanDataTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Export/SpanExporterTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Internal/ConcurrentIntrusiveListTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/LinkTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/MessageEventTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/NoopSpan.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/B3FormatTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/SpanContextParseExceptionTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/TextFormatBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Propagation/TraceContextTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/Sampler/SamplersTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/SpanBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/SpanBuilderBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/SpanBuilderTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/SpanContextTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/SpanIdTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/SpanTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/StatusTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/TraceComponentTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/TraceIdTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/TraceOptionsTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/TracerBaseTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/TracerTest.cs create mode 100644 test/OpenCensus.Tests/Impl/Trace/TracingTest.cs create mode 100644 test/OpenCensus.Tests/OpenCensus.Tests.csproj create mode 100644 test/OpenCensus.Tests/xunit.runner.json create mode 100644 test/TestApp.AspNetCore.2.0/CallbackMiddleware.cs create mode 100644 test/TestApp.AspNetCore.2.0/Controllers/ForwardController.cs create mode 100644 test/TestApp.AspNetCore.2.0/Controllers/ValuesController.cs create mode 100644 test/TestApp.AspNetCore.2.0/Program.cs create mode 100644 test/TestApp.AspNetCore.2.0/Startup.cs create mode 100644 test/TestApp.AspNetCore.2.0/TestApp.AspNetCore.2.0.csproj create mode 100644 test/TestApp.AspNetCore.2.0/appsettings.Development.json create mode 100644 test/TestApp.AspNetCore.2.0/appsettings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3e759b75b --- /dev/null +++ b/.gitignore @@ -0,0 +1,330 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..6b99a7d47 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/test/OpenCensus.Collector.Dependencies.Tests/bin/Debug/netcoreapp2.0/OpenCensus.Collector.Dependencies.Tests.dll", + "args": [], + "cwd": "${workspaceFolder}/test/OpenCensus.Collector.Dependencies.Tests", + // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ,] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..b293cfaf5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "rewrap.wrappingColumn": 79, + "files.associations": { + ".vsts/*.yml": "azure-pipelines" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..a914fef13 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet build", + "type": "shell", + "group": "build", + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/.vsts/ci-build.yml b/.vsts/ci-build.yml new file mode 100644 index 000000000..01ce033fd --- /dev/null +++ b/.vsts/ci-build.yml @@ -0,0 +1,60 @@ +# CI build. No publioshing of artifacts + +variables: + DotNetVersion: "2.2.101" + +pr: [ 'master', 'develop' ] + +jobs: + +- job: Windows + pool: Hosted VS2017 + steps: + + - task: DotNetCoreInstaller@0 + displayName: force use of desired dotnet version + inputs: + version: $(DotNetVersion) + + # "restore" is run automatically by "build" + - task: DotNetCoreCLI@2 + displayName: build solution (Release) + inputs: + command: "build" + projects: "OpenCensus.proj" + arguments: "--configuration Release" + + # consider switch to https://docs.microsoft.com/vsts/pipelines/tasks/test/vstest?view=vsts + - task: DotNetCoreCLI@2 + displayName: test + inputs: + command: "test" + projects: "test/**/*.Tests.csproj" + arguments: "--configuration Release" + + - task: PublishTestResults@2 + +- job: Linux + pool: Hosted Ubuntu 1604 + steps: + - task: DotNetCoreInstaller@0 + displayName: force use of desired dotnet version + inputs: + version: $(DotNetVersion) + + # "restore" is run automatically by "build" + - task: DotNetCoreCLI@2 + displayName: build + inputs: + command: "build" + projects: "OpenCensus.proj" + arguments: "--configuration Release" + + - task: DotNetCoreCLI@2 + displayName: test + inputs: + command: "test" + projects: "test/**/*.Tests.csproj" + arguments: "--configuration Release" + + - task: PublishTestResults@2 \ No newline at end of file diff --git a/.vsts/ci-myget-update.yml b/.vsts/ci-myget-update.yml new file mode 100644 index 000000000..786c569d0 --- /dev/null +++ b/.vsts/ci-myget-update.yml @@ -0,0 +1,51 @@ +# CI build with the upload to MyGet + +variables: + DotNetVersion: "2.2.101" + +trigger: + branches: + include: + - master + - develop + +queue: Hosted VS2017 + +steps: +- task: DotNetCoreInstaller@0 + displayName: force use of desired dotnet version + inputs: + version: $(DotNetVersion) + +# "build" and "restore" are run by "pack". +- task: DotNetCoreCLI@2 + displayName: pack solution with symbols (Release) + inputs: + command: "pack" + projects: "OpenCensus.proj" + configuration: 'Release' + packDirectory: '$(build.artifactStagingDirectory)' + buildProperties: "SymbolPackageFormat=snupkg" + +- task: DotNetCoreCLI@2 + displayName: test + inputs: + command: "test" + projects: "test/**/*.Tests.csproj" + arguments: "--configuration Release" + +- task: PublishTestResults@2 + +- task: NuGetCommand@2 + displayName: 'Publish nugets to MyGet' + inputs: + command: push + nuGetFeedType: external + publishFeedCredentials: 'myget-open-census' + +# this task is required as symbols packages needs to be published manually. +- task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: "$(build.artifactstagingdirectory)" + ArtifactName: "drop" + ArtifactType: "Container" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..2978bb73b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +Please update changelog as part of any significant pull request. Place short +description of your change into "Unreleased" section. As part of release +process content of "Unreleased" section content will generate release notes for +the release. + +## Unreleased + +- API improvements - use C# native classes to measure time. +- OpenCensus.Collectors.AspNetCore: Allow to supply custom sampler based on request properties using custom code. For instance filter out telemetry from specific path. +- OpenCensus.Collectors.Dependencies: Allow to supply custom sampler based on request properties using custom code. By default, filter out calls to Zipkin REST endpoint from the exporter. + +## 0.1.0-alpha-42253 + +Release [01/18/2019](https://github.com/census-instrumentation/opencensus-csharp/releases/tag/0.1.0-alpha-42253). + +- Application Insights exporter improvements - now understands http attributes + and process links, annotations and messages. +- ASP.NET Core collector now uses `http.route` for the span name. +- Initial implementation of Resource Specification. +- Plug in to collect Redis calls made using StackExchange.Redis package. +- Object of type `ISpanData` can be created using only Abstractions package. +- Number of minor APIs adjustments. + +## 0.1.0-alpha-33381 + +Released +[12/18/2018](https://github.com/census-instrumentation/opencensus-csharp/releases/tag/0.1.0-alpha-33381). + +- Collectors for ASP.NET Core and .NET Core HttpClient. +- Initial version of Ocagent exporter implemented. +- Initial version of StackDriver exporter implemented. +- Support double attributes according to the [spec + change](https://github.com/census-instrumentation/opencensus-specs/issues/172). +- Initial implementation of Prometheus exporter. +- Initial version of Application Insights exporter implemented. +- Zipkin exporter implemented. +- Initial version of SDK published. It is based on contribution from Pivotal + [from](https://github.com/SteeltoeOSS/Management/tree/dev/src/Steeltoe.Management.OpenCensus). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..3bc61319b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing + +## Report a bug or requesting feature + +Reporting bug is an important contribution. Please make sure to include: + +- expected and actual behavior. +- dotnet version that application is compiled on and running with (it may be + different - for instance target framework was set to .NET 4.6 for + compilaiton, but applicaiton is running on .NET 4.7.3). +- exception call stack and other artifacts. +- if possible - reporo application and steps to reproduce. + +## How to contribute + +### Before started + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +### Build + +You can use Visual Studio 2017 or VS code to contribute. Just open root folder +or `OpenCensus.sln` in your editor and follow normal development process. + +To build from command line you need `dotnet` version `2.0+`. + +``` sh +dotnet build OpenCensus.sln +``` + +### Test + +You can use Visual Studio 2017 or VS code to test your contribution. Open root +folder or `OpenCensus.sln` in your editor and follow normal development +process. + +To test from command line you need `dotnet` version `2.0+`. + +``` sh +dotnet test OpenCensus.sln +``` + +### Proposing changes + +Create a Pull Request with your changes. Please add any user-visible changes to +`CHANGELOG.md`. The continuous integration build will run the tests and static +analysis. It will also check that the pull request branch has no merge commits. +When the changes are accepted, they will be merged or cherry-picked by an +OpenCensus repository maintainers. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 000000000..cfa230142 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/OpenCensus.proj b/OpenCensus.proj new file mode 100644 index 000000000..77112281b --- /dev/null +++ b/OpenCensus.proj @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenCensus.sln b/OpenCensus.sln new file mode 100644 index 000000000..e4b934222 --- /dev/null +++ b/OpenCensus.sln @@ -0,0 +1,158 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28407.52 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus", "src\OpenCensus\OpenCensus.csproj", "{AE3E3DF5-4083-4C6E-A840-8271B0ACDE7E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Tests", "test\OpenCensus.Tests\OpenCensus.Tests.csproj", "{CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B7408D66-487A-40E1-BDB7-BC17BD28F721}" + ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md + CONTRIBUTING.md = CONTRIBUTING.md + NuGet.config = NuGet.config + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E-03FA-4FFF-89A5-C51F107623FD}" + ProjectSection(SolutionItems) = preProject + build\Common.prod.props = build\Common.prod.props + build\Common.test.props = build\Common.test.props + build\debug.snk = build\debug.snk + build\OpenCensus.prod.loose.ruleset = build\OpenCensus.prod.loose.ruleset + build\OpenCensus.prod.ruleset = build\OpenCensus.prod.ruleset + build\OpenCensus.test.ruleset = build\OpenCensus.test.ruleset + build\stylecop.json = build\stylecop.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Zipkin", "src\OpenCensus.Exporter.Zipkin\OpenCensus.Exporter.Zipkin.csproj", "{7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vsts", ".vsts", "{61188153-47FB-4567-AC9B-79B2435853EB}" + ProjectSection(SolutionItems) = preProject + .vsts\ci-build.yml = .vsts\ci-build.yml + .vsts\ci-myget-update.yml = .vsts\ci-myget-update.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.ApplicationInsights", "src\OpenCensus.Exporter.ApplicationInsights\OpenCensus.Exporter.ApplicationInsights.csproj", "{4493F5D9-874E-4FBF-B2F3-37890BD910E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Stackdriver", "src\OpenCensus.Exporter.Stackdriver\OpenCensus.Exporter.Stackdriver.csproj", "{DE1B4783-C01F-4672-A6EB-695F1717105B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "src\Samples\Samples.csproj", "{C58393EB-32E2-4AC6-9170-697B36306E15}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Abstractions", "src\OpenCensus.Abstractions\OpenCensus.Abstractions.csproj", "{99F8A331-05E9-45A5-89BA-4C54E825E5B2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Prometheus", "src\OpenCensus.Exporter.Prometheus\OpenCensus.Exporter.Prometheus.csproj", "{AD9B2B54-EC9C-448E-BD3C-EDCC3F7AD022}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Collector.Dependencies", "src\OpenCensus.Collector.Dependencies\OpenCensus.Collector.Dependencies.csproj", "{D3FFBC59-2486-4F8F-BFF1-FA95C84929E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Collector.Dependencies.Tests", "test\OpenCensus.Collector.Dependencies.Tests\OpenCensus.Collector.Dependencies.Tests.csproj", "{56E2647A-B814-4BAC-B854-BEA0051B5F2E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Collector.AspNetCore", "src\OpenCensus.Collector.AspNetCore\OpenCensus.Collector.AspNetCore.csproj", "{752D2182-A351-41D8-99EE-DD363D7D5B43}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Collector.AspNetCore.Tests", "test\OpenCensus.Collector.AspNetCore.Tests\OpenCensus.Collector.AspNetCore.Tests.csproj", "{2A47F6A8-63E5-4237-8046-94CAF321E797}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testdata", "testdata", "{77C7929A-2EED-4AA6-8705-B5C443C8AA0F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore.2.0", "test\TestApp.AspNetCore.2.0\TestApp.AspNetCore.2.0.csproj", "{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Ocagent", "src\OpenCensus.Exporter.Ocagent\OpenCensus.Exporter.Ocagent.csproj", "{56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.Stackdriver.Tests", "test\OpenCensus.Exporter.Stackdriver.Tests\OpenCensus.Exporter.Stackdriver.Tests.csproj", "{6875032B-DFDC-4CDE-A283-37CA7F99926A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Exporter.ApplicationInsights.Tests", "test\OpenCensus.Exporter.ApplicationInsights.Tests\OpenCensus.Exporter.ApplicationInsights.Tests.csproj", "{1FA1F509-7722-48E3-9A35-16CBB6774957}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Collector.StackExchangeRedis", "src\OpenCensus.Collector.StackExchangeRedis\OpenCensus.Collector.StackExchangeRedis.csproj", "{6B681D72-D68A-44CC-8C75-53B9A322E6EC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenCensus.Collector.StackExchangeRedis.Tests", "test\OpenCensus.Collector.StackExchangeRedis.Tests\OpenCensus.Collector.StackExchangeRedis.Tests.csproj", "{CA98AF29-0852-4ADD-A66B-7E96266EE7B7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE3E3DF5-4083-4C6E-A840-8271B0ACDE7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE3E3DF5-4083-4C6E-A840-8271B0ACDE7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE3E3DF5-4083-4C6E-A840-8271B0ACDE7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE3E3DF5-4083-4C6E-A840-8271B0ACDE7E}.Release|Any CPU.Build.0 = Release|Any CPU + {CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}.Release|Any CPU.Build.0 = Release|Any CPU + {7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Release|Any CPU.Build.0 = Release|Any CPU + {4493F5D9-874E-4FBF-B2F3-37890BD910E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4493F5D9-874E-4FBF-B2F3-37890BD910E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4493F5D9-874E-4FBF-B2F3-37890BD910E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4493F5D9-874E-4FBF-B2F3-37890BD910E0}.Release|Any CPU.Build.0 = Release|Any CPU + {DE1B4783-C01F-4672-A6EB-695F1717105B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE1B4783-C01F-4672-A6EB-695F1717105B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE1B4783-C01F-4672-A6EB-695F1717105B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE1B4783-C01F-4672-A6EB-695F1717105B}.Release|Any CPU.Build.0 = Release|Any CPU + {C58393EB-32E2-4AC6-9170-697B36306E15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C58393EB-32E2-4AC6-9170-697B36306E15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C58393EB-32E2-4AC6-9170-697B36306E15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C58393EB-32E2-4AC6-9170-697B36306E15}.Release|Any CPU.Build.0 = Release|Any CPU + {99F8A331-05E9-45A5-89BA-4C54E825E5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99F8A331-05E9-45A5-89BA-4C54E825E5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99F8A331-05E9-45A5-89BA-4C54E825E5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99F8A331-05E9-45A5-89BA-4C54E825E5B2}.Release|Any CPU.Build.0 = Release|Any CPU + {AD9B2B54-EC9C-448E-BD3C-EDCC3F7AD022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD9B2B54-EC9C-448E-BD3C-EDCC3F7AD022}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD9B2B54-EC9C-448E-BD3C-EDCC3F7AD022}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD9B2B54-EC9C-448E-BD3C-EDCC3F7AD022}.Release|Any CPU.Build.0 = Release|Any CPU + {D3FFBC59-2486-4F8F-BFF1-FA95C84929E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3FFBC59-2486-4F8F-BFF1-FA95C84929E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3FFBC59-2486-4F8F-BFF1-FA95C84929E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3FFBC59-2486-4F8F-BFF1-FA95C84929E1}.Release|Any CPU.Build.0 = Release|Any CPU + {56E2647A-B814-4BAC-B854-BEA0051B5F2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56E2647A-B814-4BAC-B854-BEA0051B5F2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56E2647A-B814-4BAC-B854-BEA0051B5F2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56E2647A-B814-4BAC-B854-BEA0051B5F2E}.Release|Any CPU.Build.0 = Release|Any CPU + {752D2182-A351-41D8-99EE-DD363D7D5B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {752D2182-A351-41D8-99EE-DD363D7D5B43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {752D2182-A351-41D8-99EE-DD363D7D5B43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {752D2182-A351-41D8-99EE-DD363D7D5B43}.Release|Any CPU.Build.0 = Release|Any CPU + {2A47F6A8-63E5-4237-8046-94CAF321E797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A47F6A8-63E5-4237-8046-94CAF321E797}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A47F6A8-63E5-4237-8046-94CAF321E797}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A47F6A8-63E5-4237-8046-94CAF321E797}.Release|Any CPU.Build.0 = Release|Any CPU + {F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Release|Any CPU.Build.0 = Release|Any CPU + {56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56B0ED25-8A14-4AA2-B59D-FAAFCBACDD4A}.Release|Any CPU.Build.0 = Release|Any CPU + {6875032B-DFDC-4CDE-A283-37CA7F99926A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6875032B-DFDC-4CDE-A283-37CA7F99926A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6875032B-DFDC-4CDE-A283-37CA7F99926A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6875032B-DFDC-4CDE-A283-37CA7F99926A}.Release|Any CPU.Build.0 = Release|Any CPU + {1FA1F509-7722-48E3-9A35-16CBB6774957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FA1F509-7722-48E3-9A35-16CBB6774957}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FA1F509-7722-48E3-9A35-16CBB6774957}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FA1F509-7722-48E3-9A35-16CBB6774957}.Release|Any CPU.Build.0 = Release|Any CPU + {6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Release|Any CPU.Build.0 = Release|Any CPU + {CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7CB2F02E-03FA-4FFF-89A5-C51F107623FD} = {61188153-47FB-4567-AC9B-79B2435853EB} + {F2F81E76-6A0E-466B-B673-EBBF1A9ED075} = {77C7929A-2EED-4AA6-8705-B5C443C8AA0F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index c0079e7de..9d95ee65e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,315 @@ -# OpenTelemetry .NET SDK +# OpenCensus .NET SDK - distributed tracing and stats collection framework -This repository contains .NET version of OpenTelemetry SDK. \ No newline at end of file +[![Gitter chat][gitter-image]][gitter-url] +[![Build Status](https://opencensus.visualstudio.com/continuous-integration/_apis/build/status/ci-myget-update.yml)](https://opencensus.visualstudio.com/continuous-integration/_build/latest?definitionId=3) + +OpenCensus is a toolkit for collecting application performance and behavior +data. It currently includes 3 APIs: stats, tracing and tags. + +The library is in [Beta](#versioning) stage and APIs are expected to be mostly +stable. The library is expected to move to [GA](#versioning) stage after v1.0.0 +major release. + +Please join [gitter](https://gitter.im/census-instrumentation/Lobby) for help +or feedback on this project. + +We encourage contributions. Use tags [up-for-grabs][up-for-grabs-issues] and +[good first issue][good-first-issues] to get started with the project. Follow +[CONTRIBUTING](CONTRIBUTING.md) guide to report issues or submit a proposal. + +## Packages + +### API and implementation + +| Package | MyGet (CI) | NuGet (releases) | +| ----------------------- | ---------------- | -----------------| +| OpenCensus | [![MyGet Nightly][opencensus-myget-image]][opencensus-myget-url] | [![NuGet Release][opencensus-nuget-image]][opencensus-nuget-url] | +| OpenCensus.Abstractions | [![MyGet Nightly][opencensus-abs-myget-image]][opencensus-abs-myget-url] | [![NuGet Release][opencensus-abs-nuget-image]][opencensus-abs-nuget-url] | + +### Data Collectors + +| Package | MyGet (CI) | NuGet (releases) | +| ----------------------- | ---------------- | -----------------| +| Asp.Net Core | [![MyGet Nightly][opencensus-collect-aspnetcore-myget-image]][opencensus-collect-aspnetcore-myget-url] | [![NuGet Release][opencensus-collect-aspnetcore-nuget-image]][opencensus-collect-aspnetcore-nuget-url] | +| .Net Core HttpClient | [![MyGet Nightly][opencensus-collect-deps-myget-image]][opencensus-collect-deps-myget-url] | [![NuGet Release][opencensus-collect-deps-nuget-image]][opencensus-collect-deps-nuget-url] | +| StackExchange.Redis | [![MyGet Nightly][opencensus-collect-stackexchange-redis-myget-image]][opencensus-collect-stackexchange-redis-myget-url] | [![NuGet Release][opencensus-collect-stackexchange-redis-nuget-image]][opencensus-collect-stackexchange-redis-nuget-url]| + +### Exporters Packages + +| Package | MyGet (CI) | NuGet (releases) | +| ----------------------- | ---------------- | -----------------| +| Zipkin | [![MyGet Nightly][opencensus-exporter-zipkin-myget-image]][opencensus-exporter-zipkin-myget-url] | [![NuGet release][opencensus-exporter-zipkin-nuget-image]][opencensus-exporter-zipkin-nuget-url] | +| Prometheus | [![MyGet Nightly][opencensus-exporter-prom-myget-image]][opencensus-exporter-prom-myget-url] | [![NuGet release][opencensus-exporter-prom-nuget-image]][opencensus-exporter-prom-nuget-url] | +| Application Insights | [![MyGet Nightly][opencensus-exporter-ai-myget-image]][opencensus-exporter-ai-myget-url] | [![NuGet release][opencensus-exporter-ai-nuget-image]][opencensus-exporter-ai-nuget-url] | +| Stackdriver | [![MyGet Nightly][opencensus-exporter-stackdriver-myget-image]][opencensus-exporter-stackdriver-myget-url] | [![NuGet release][opencensus-exporter-stackdriver-nuget-image]][opencensus-exporter-stackdriver-nuget-url] | + +## OpenCensus QuickStart: collecting data + +You can use Open Census API to instrument code and report data. Or use one of +automatic data collection modules. + +### Using ASP.NET Core incoming requests collector + +Incoming requests of ASP.NET Core app can be automatically tracked. + +1. Install packages to your project: + [OpenCensus][opencensus-nuget-url] + [OpenCensus.Collector.AspNetCore][opencensus-collect-aspnetcore-nuget-url] + +2. Make sure `ITracer`, `ISampler`, and `IPropagationComponent` registered in DI. + ``` csharp + services.AddSingleton(Tracing.Tracer); + services.AddSingleton(Samplers.AlwaysSample); + services.AddSingleton(new DefaultPropagationComponent()); + ``` + +3. Configure data collection singletons in ConfigureServices method: + ``` csharp + public void ConfigureServices(IServiceCollection services) + { + // ... + services.AddSingleton(new RequestsCollectorOptions()); + services.AddSingleton(); + ``` + +4. Initialize data collection by instantiating singleton in Configure method + ``` csharp + public void Configure(IApplicationBuilder app, /*... other arguments*/ ) + { + // ... + var collector = app.ApplicationServices.GetService(); + ``` + +### Using Dependencies collector + +Outgoing http calls made by .NET Core `HttpClient` can be automatically tracked. + +1. Install package to your project: + [OpenCensus.Collector.Dependencies][opencensus-collect-deps-nuget-url] + +2. Make sure `ITracer`, `ISampler`, and `IPropagationComponent` registered in DI. + ``` csharp + services.AddSingleton(Tracing.Tracer); + services.AddSingleton(Samplers.AlwaysSample); + services.AddSingleton(new DefaultPropagationComponent()); + ``` + +3. Configure data collection singletons in ConfigureServices method: + ``` csharp + public void ConfigureServices(IServiceCollection services) + { + // ... + services.AddSingleton(new DependenciesCollectorOptions()); + services.AddSingleton(); + ``` + +4. Initiate data collection by instantiating singleton in Configure method + ``` csharp + public void Configure(IApplicationBuilder app, /*... other arguments*/ ) + { + // ... + var depCollector = app.ApplicationServices.GetService(); + ``` + +### Using StackExchange.Redis collector + +Outgoing http calls to Redis made usign StackExchange.Redis library can be automatically tracked. + +1. Install package to your project: + [OpenCensus.Collector.StackExchangeRedis][opencensus-collect-stackexchange-redis-nuget-url] + +2. Make sure `ITracer`, `ISampler`, and `IExportComponent` registered in DI. + ``` csharp + services.AddSingleton(Tracing.Tracer); + services.AddSingleton(Samplers.AlwaysSample); + services.AddSingleton(Tracing.ExportComponent); + ``` + +3. Configure data collection singletons in ConfigureServices method: + ``` csharp + public void ConfigureServices(IServiceCollection services) + { + // ... + services.AddSingleton(new StackExchangeRedisCallsCollectorOptions()); + services.AddSingleton(); + ``` + +4. Initiate data collection by instantiating singleton in Configure method + ``` csharp + public void Configure(IApplicationBuilder app, /*... other arguments*/ ) + { + // ... + var redisCollector = app.ApplicationServices.GetService(); + + // use collector to configure the profiler + ConnectionMultiplexer connection = ConnectionMultiplexer.Connect("localhost:6379"); + connection.RegisterProfiler(redisCollector.GetProfilerSessionsFactory()); + ``` + +## OpenCensus QuickStart: exporting data + +### Using Zipkin exporter + +Configure Zipkin exporter to see traces in Zipkin UI. + +1. Get Zipkin using [getting started guide][zipkin-get-started]. +2. Start `ZipkinTraceExporter` as below: +3. See [sample][zipkin-sample] for example use. + +``` csharp +var exporter = new ZipkinTraceExporter( + new ZipkinTraceExporterOptions() { + Endpoint = new Uri("https:///api/v2/spans"), + ServiceName = typeof(Program).Assembly.GetName().Name, + }, + Tracing.ExportComponent); +exporter.Start(); + +var span = tracer + .SpanBuilder("incoming request") + .SetSampler(Samplers.AlwaysSample) + .StartSpan(); + +Thread.Sleep(TimeSpan.FromSeconds(1)); +span.End(); +``` + +### Using Prometheus exporter + +Configure Prometheus exporter to have stats collected by Prometheus. + +1. Get Prometheus using [getting started guide][prometheus-get-started]. +2. Start `PrometheusExporter` as below. +3. See [sample][prometheus-sample] for example use. + +``` csharp +var exporter = new PrometheusExporter( + new PrometheusExporterOptions() + { + Url = new Uri("http://localhost:9184/metrics/") + }, + Stats.ViewManager); + +exporter.Start(); + +try +{ + // record metrics + statsRecorder.NewMeasureMap().Put(VideoSize, values[0] * MiB).Record(); +} +finally +{ + exporter.Stop(); +} +``` + +### Using Stackdriver Exporter + +This sample assumes your code authenticates to Stackdriver APIs using [service account][gcp-auth] with +credentials stored in environment variable GOOGLE_APPLICATION_CREDENTIALS. +When you run on [GAE][GAE], [GKE][GKE] or locally with gcloud sdk installed - this is typically the case. +There is also a constructor for specifying path to the service account credential. See [sample][stackdriver-sample] for details. + +1. Add [Stackdriver Exporter package][opencensus-exporter-stackdriver-myget-url] reference. +2. Enable [Stackdriver Trace][stackdriver-trace-setup] API. +3. Enable [Stackdriver Monitoring][stackdriver-monitoring-setup] API. +4. Instantiate a new instance of `StackdriverExporter` with your Google Cloud's ProjectId +5. See [sample][stackdriver-sample] for example use. + +``` csharp + var exporter = new StackdriverExporter( + "YOUR-GOOGLE-PROJECT-ID", + Tracing.ExportComponent, + Stats.ViewManager); + exporter.Start(); +``` + +### Using Application Insights exporter + +1. Create [Application Insights][ai-get-started] resource. +2. Set instrumentation key via telemetry configuration object + (`new TelemetryConfiguration("iKey")`). This object may be injected via + dependency injection as well. +3. Instantiate a new instance of `ApplicationInsightsExporter`. +4. See [sample][ai-sample] for example use. + +``` csharp +var config = new TelemetryConfiguration("iKey") +var exporter = new ApplicationInsightsExporter( + Tracing.ExportComponent, + Stats.ViewManager, + config); // either global or local config can be used +exporter.Start(); +``` + +## Versioning + +This library follows [Semantic Versioning][semver]. + +**GA**: Libraries defined at a GA quality level are stable, and will not +introduce backwards-incompatible changes in any minor or patch releases. We +will address issues and requests with the highest priority. If we were to make +a backwards-incompatible changes on an API, we will first mark the existing API +as deprecated and keep it for 18 months before removing it. + +**Beta**: Libraries defined at a Beta quality level are expected to be mostly +stable and we're working towards their release candidate. We will address +issues and requests with a higher priority. There may be backwards incompatible +changes in a minor version release, though not in a patch release. If an +element is part of an API that is only meant to be used by exporters or other +opencensus libraries, then there is no deprecation period. Otherwise, we will +deprecate it for 18 months before removing it, if possible. + +[gitter-image]: https://badges.gitter.im/census-instrumentation/lobby.svg +[gitter-url]:https://gitter.im/census-instrumentation/lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +[opencensus-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.svg +[opencensus-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus +[opencensus-abs-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Abstractions.svg +[opencensus-abs-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Abstractions +[opencensus-exporter-zipkin-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Exporter.Zipkin.svg +[opencensus-exporter-zipkin-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Exporter.Zipkin +[opencensus-exporter-prom-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Exporter.Prometheus.svg +[opencensus-exporter-prom-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Exporter.Prometheus +[opencensus-exporter-ai-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Exporter.ApplicationInsights.svg +[opencensus-exporter-ai-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Exporter.ApplicationInsights +[opencensus-exporter-stackdriver-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Exporter.Stackdriver.svg +[opencensus-exporter-stackdriver-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Exporter.Stackdriver +[opencensus-collect-aspnetcore-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Collector.AspNetCore.svg +[opencensus-collect-aspnetcore-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Collector.AspNetCore +[opencensus-collect-deps-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Collector.Dependencies.svg +[opencensus-collect-deps-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Collector.Dependencies +[opencensus-collect-stackexchange-redis-myget-image]:https://img.shields.io/myget/opencensus/vpre/OpenCensus.Collector.StackExchangeRedis.svg +[opencensus-collect-stackexchange-redis-myget-url]: https://www.myget.org/feed/opencensus/package/nuget/OpenCensus.Collector.StackExchangeRedis +[opencensus-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.svg +[opencensus-nuget-url]:https://www.nuget.org/packages/OpenCensus +[opencensus-abs-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Abstractions.svg +[opencensus-abs-nuget-url]: https://www.nuget.org/packages/OpenCensus.Abstractions +[opencensus-exporter-zipkin-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Exporter.Zipkin.svg +[opencensus-exporter-zipkin-nuget-url]: https://www.nuget.org/packages/OpenCensus.Exporter.Zipkin +[opencensus-exporter-prom-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Exporter.Prometheus.svg +[opencensus-exporter-prom-nuget-url]: https://www.nuget.org/packages/OpenCensus.Exporter.Prometheus +[opencensus-exporter-ai-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Exporter.ApplicationInsights.svg +[opencensus-exporter-ai-nuget-url]: https://www.nuget.org/packages/OpenCensus.Exporter.ApplicationInsights +[opencensus-exporter-stackdriver-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Exporter.Stackdriver.svg +[opencensus-exporter-stackdriver-nuget-url]: https://www.nuget.org/packages/OpenCensus.Exporter.Stackdriver +[opencensus-collect-aspnetcore-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Collector.AspNetCore.svg +[opencensus-collect-aspnetcore-nuget-url]: https://www.nuget.org/packages/OpenCensus.Collector.AspNetCore +[opencensus-collect-deps-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Collector.Dependencies.svg +[opencensus-collect-deps-nuget-url]: https://www.nuget.org/packages/OpenCensus.Collector.Dependencies +[opencensus-collect-stackexchange-redis-nuget-image]:https://img.shields.io/nuget/vpre/OpenCensus.Collector.StackExchangeRedis.svg +[opencensus-collect-stackexchange-redis-nuget-url]: https://www.nuget.org/packages/OpenCensus.Collector.StackExchangeRedis +[up-for-grabs-issues]: https://github.com/census-instrumentation/opencensus-csharp/issues?q=is%3Aissue+is%3Aopen+label%3Aup-for-grabs +[good-first-issues]: https://github.com/census-instrumentation/opencensus-csharp/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 +[zipkin-get-started]: https://zipkin.io/pages/quickstart.html +[ai-get-started]: https://docs.microsoft.com/azure/application-insights +[stackdriver-trace-setup]: https://cloud.google.com/trace/docs/setup/ +[stackdriver-monitoring-setup]: https://cloud.google.com/monitoring/api/enable-api +[GAE]: https://cloud.google.com/appengine/docs/flexible/dotnet/quickstart +[GKE]: https://codelabs.developers.google.com/codelabs/cloud-kubernetes-aspnetcore/index.html?index=..%2F..index#0 +[gcp-auth]: https://cloud.google.com/docs/authentication/getting-started +[semver]: http://semver.org/ +[ai-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestApplicationInsights.cs +[stackdriver-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestStackdriver.cs +[zipkin-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestZipkin.cs +[prometheus-get-started]: https://prometheus.io/docs/introduction/first_steps/ +[prometheus-sample]: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/src/Samples/TestPrometheus.cs \ No newline at end of file diff --git a/build/Common.prod.props b/build/Common.prod.props new file mode 100644 index 000000000..4d6ef0efc --- /dev/null +++ b/build/Common.prod.props @@ -0,0 +1,62 @@ + + + + $(MSBuildThisFileDirectory)/OpenCensus.prod.ruleset + $(OutputPath)/$(TargetFramework)/$(AssemblyName).xml + $(Build_ArtifactStagingDirectory) + + + + true + $(MSBuildThisFileDirectory)/debug.snk + false + $(DefineConstants);SIGNED + + + + + + + + + 0 + 1 + 0 + + alpha + + 2018-08-25 + + $([MSBuild]::Divide($([System.DateTime]::Now.Subtract($([System.DateTime]::Parse($(SemanticVersionDate)))).TotalMinutes), 5).ToString('F0')) + + + + $(SemanticVersionMajor).$(SemanticVersionMinor).$(SemanticVersionPatch) + $(PreReleaseMilestone)-$(PreReleaseVersion) + $(SemanticVersionMajor).$(SemanticVersionMinor).$(SemanticVersionPatch).$(PreReleaseVersion) + + + + + true + + + + true + + + + + + + + \ No newline at end of file diff --git a/build/Common.test.props b/build/Common.test.props new file mode 100644 index 000000000..7e8bef3ca --- /dev/null +++ b/build/Common.test.props @@ -0,0 +1,18 @@ + + + + true + $(MSBuildThisFileDirectory)/debug.snk + false + $(DefineConstants);SIGNED + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenCensus.sln'))\build\OpenCensus.test.ruleset + + + + + + + \ No newline at end of file diff --git a/build/OpenCensus.prod.loose.ruleset b/build/OpenCensus.prod.loose.ruleset new file mode 100644 index 000000000..623b883eb --- /dev/null +++ b/build/OpenCensus.prod.loose.ruleset @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/OpenCensus.prod.ruleset b/build/OpenCensus.prod.ruleset new file mode 100644 index 000000000..6496e9b16 --- /dev/null +++ b/build/OpenCensus.prod.ruleset @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/OpenCensus.test.ruleset b/build/OpenCensus.test.ruleset new file mode 100644 index 000000000..44320942d --- /dev/null +++ b/build/OpenCensus.test.ruleset @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/debug.snk b/build/debug.snk new file mode 100644 index 0000000000000000000000000000000000000000..037398aa0690939e11087bed59c46eec0e988223 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096|!B#2>53tGekpU_)8H-+BnqcX;52Dw! zG|A=6w^_i+<|FPlK+#B;k3O9GU-{zNvZk%<_FC7VH&)##g1hYPyd)6{z-p+-k-WR(^>3<-p{`uqaLp8S4Xsr}8D7Se;*q12b2|xz{dU zOK?w${Sea}v#evHsSx6sU=O_2>5&`}n&w18Y6p|A`rwrZVK6B?6z)eBIHD~NhIvoH z77QaUHNb@q^9JM2=zytKF`T53aX7;vl%6;g*CDG$=(BJZE^g2=Niu8lE#p4rZ? zJro2AXV@3he+IdEOOVy=A+gbWeyJD+!c0ebY%0Yczop4t+@&s_avGb-WXJ`F<`PM# iOdp?81Ver@U=gSIcMFKLgrrE< zxzX?Mod0>A7w3iId`9lQ?!DGtd#&rbR+N^8GCqhJgo1*C|5WA4GZYlm=fJ;*u~C7) z4g9NAfFBRMo+-& z%{_f0r{iz7mwgeiFatl>Y#hB|-_PZ5^$UXA2cH|Ot_?VQ$-G=J7T&+)wYNmSvJ1QOPZHY_X@xi^IkUr3&| zE^X38qK-b0Bf?^6!P;y&Z{Fk}1BJ`yuM-J?fgf5NFUWeP=HOJJOdIqdkVR+UeoZQFnKD8LZ;S+RG7|x>kl{0z{3oM6$W?PxFcZ((L!2rrQIW zx08$%4KopjpJvO1=pr<34i78)Z9tfzyTotg^9A&n5UBc1-WhZ5KYTZCZ*HHvTW@4N z_x1Af3UKD4s|2;9@|{@O-ocGo9C!00T~!&sHtWjB6bjdlo@6o zGpgH;9sow2+lgD%JAq-_q|6hP%SnidVF$Z3Ef&h=Ydw1oUBoPO^KneMibsMcM|);x zWK3tx0F1biq=bgpu9Wo`~5Q-2ZlmDWMP29x6pvl9IL zmUTvRYnb8kMX_Pf#qYV$Y-)z1@(N5*k<*qd3h4mS^inlHL{6*lTj(O`Yn?z^-z!md z3a*8AXT9lhiE8R>CkqP{)Vb+t-BT67odJa&2&Yq4SZ_pN8CTzwn0orlfJ0&dVJQ=> zt&(4`1n`**3pyQtDL-uHFp>0>M}9DhG+b(Ki(q168+weYk~v%?mgy zt!@#0AW>GYERWPbM(M-Co*khu8R*0+462A=v$Jusdwh08B_3RCW{4cCuO{_x!V?R| z3V%r~k6?z83JM5M;>*xxe?bf8mIziZ&VzH1Q&9A)NRfe3KE6lxO?{1l4h!U#1#PP? zN)YwUaLhr;RMneD^603o*bfw8)90=STlD3d|#r{Y61gTT=Ho zslN&vDCfg6zrLHWR7KO@wm{)Dkr(@$}rG)7DW32vz=bR4dYJKoF zl}}`@XuTTjWP_#;hCqNs~U;S&yrR=Rt2>WN&SKNJ=Cr)86x?=mtu##_M9C$ zJrj8JW5p@zGWhJ1OsVv(u60mNhGYO%;p8t#dcrskrK0x#d=Tr1s(4OERg=&D%LC#0 zTKZ zPh>p=8|qra^C6Jz|3wxQ_bRC0;m4S<6+)mZ;XFZgC|38m-*Yw^g7`-{Ib3cxsGVAM z8psIeNZB4|Q>M zIYzju8WSu58ASzumntqKs`R{FeNv@MtEggl>_a%8x1Ae^Q5AbYcFIZA*!3YjIJ^)= z`>m=J^z|c;sn1GHuh4=~#)K&GP_>@Ee+N}8`uv}_6ptU%?tc!%w=B^s)^=K05auC@ zIK}gc)r@=Fsl`|_?e-vKK3fD!VN7whbE4XLZdj>+tBgxJ*L}EMiLpt8v5ELQwi~9X z+?!`g-!`HLu>Ui$iEwz6`Xfw-$aklfD54aPF2al?Vgzny7(()PM1j)Ds}j9VfwE}} zCFEV#n64aUQ(IeAl^nCS5}r)>5l4%BMuC9Re<#JR^}>;7!*xlXvUK(rIHR6G)-kQ= zb;;G-pY%!=Q9H^eDCYie%dPng86s9Ze7R(zWPeR#W>8A)|8j!#@u(ZFy0f-u8UDKp zX{oUMi8vE%g*}bbyjPT}5}qvI@i#iPP_-Y(J$7Q2(>7%N@3aUmPB`*_T)Ai>5*>L+ zC-{ODL5+g;wWIy zHNK(*+2i3UXmcd|@3%y|2s`Zc^Cpc{l--J(>`R>g%<+BABks}?U>05VXSTe56;?|Q zOSTTSV?t0k$^TMXYn_7;F1~_pldn{gX@^4wIf;-w<$$d|qa_DxiH#9%fM_xa8Phv? zR_6cS+i%j_2shW1NvOOi3F-__r{3t&lGhT?d7C{$(nO=$r8SW-7{g8yng89t;4M1q zopQd`^ZzpB6vfWlNh6(0f{gW18yw9fE*u4x81{F`ron^?`HNobHcSKb(HvwD&i|qW z@?pD{Ji|0!PM0)erc<)q*3&K|oTC*Cgm5}v@(+Ag%Mvb0yrQ+J21lOGqbEXm_{3yi ziIT4w!sYwf|C)OYy(#5q@PDH*1vu+rv;3zZhmW}jec`(hhyWkzFb0Cd4-(7ktm4)--mlva`qXU7+s@Qpf+e% z7|6)Z-k++o3%+{!XIEc7|5O9W38j)FPY@YUgp%zhR#7N?Ba+AT11YJe4}+gyY0S>& z#Q_G}S_+-JA4uo9?|4=WI8N^Ojv82tv4*&?@YPi;GF~!oA0+lvtR(P%8}%+#hlE@% zVy3A3STwI42+Rj{-wAAG%+8`22Q0qRzB2i@qAbE~ba>RB85T{`jESX;_Ul(B=e|=( zOY5|&mK_#MqHb7v$%zj;e~0`EL%YQE{-TD&3q~U=gNfFuHh($^3TW)@?=dhpi&NUL z*RgR<>@?%QbFp+v>RmH4H}BN~*G1lYWaD`R|HXaP1N)pz6L~CIMNaWbGPIycJ#8jspvb)YE&9WP%}hAMs$K z2H8NI2>)(?warci6&xZg08U~1yB5EI&uk1dg&O`&mGrL{&K3Elo2!4%Ok?%D5d5fV zrqR`O?=&)1-@jiQ_#!YvJNznA?zr!ckRcNyt_&YxJq%bg_pw-@Rec}QDZSN#bz8oB zE1kWOpM)6NCcH~4ugiX?PMs6884v>IzLW3g1x~Im{ovr#Z>8a=P6%d67B2nm=_9ao zE}>rbFK0N57|=qD2O|L%Qi0&+3bnjLpP@$*3Rr~CE-A^ ziSXT~#?MfVJw>W6lupzFZO2x_$Gxpx0Xzcyk>TNsSJJ0rvaI*jj4plg$keDRbv&6A zS(RWauCrPDZ;SCdbcVDoc6mn|r&Iouo}`*w>5J)M(4g^0>zpw&Mt|%XP6rr zTE^4qko`NCV1)b&i_qskUNu!d9BDKE0qkwODMz{EG-=bd1Hsz3ZX| zrrU)wExh|Dv9A!)(*?_(W+I|f_Lih{^^$;IVeEhXp_-c7ZbzN@ztgM(Z))5=yGmQ6 z&$M3IX^mLB00RLR^@VCd`5k@Rjbr%2TJY$P=V;5WHjrIR8;H2irnIzI`K>9o&99Uc zkZYO7r*PSsu_7AUhif@Av$Nad^_ur_Jg}CW4hmbC52?q>GVtax+>2Xf-?4si=EckY zGN5kXTW&jM9G!+JfToX~8OU5)6w7Dy^m%JJsz&WDTDqiPoxqT6RMa^n68Vwg?B4^) zdO764*t$Rg7CLQPwus!Ldkok2tlWyi&Z@$vluj`(-KJ;OifscQ65JnM@A z)xe%DZ>@Dkc$x`CaA$rAg{ns&e^3aX<4Xs~2>LijTYw9Iel6G8ac%ZbZKhS^GL7uNHtk3h=5cwRJ#n%lN3>F2!O z!~G&H{UXVD;Xi`}D5kETi3LYCaxRQdPYMf&2#AXI(ziQu+Bl&?!@vE}k>eDlPx0mp zGVSo@LwM+sg7iY67;?5>ocJjJV4<2FfT-%$Js`~y8tW;vO*)SXf5x#rKEqXQ#_Z|) z^za+xH}UUoB*Pg-yvc8+Q`kQBMMebRcsI9b+lYB&ngF@yjG4n7! zzKbz3GWFr%S)j#jWf>hITa+;|AU!4BwaM^mZ_r@+O4yw=%I!t__-l_Fd zk;BNATs~|d%`!E;kHn_bMG3JldQ9r?lx*voVaK)vi_8bQMFC0C_B2xe_sHk`KGrPf zBXz$+6@3bhh0kRz_dfBa&C!jJ$)Sc^8h)8dJC45FIQwi~LQ02+C#`2-=-(Wn33_mc z_%elk24j*8w@Ai)N`(-}m4_bU*uFPB=7}Xm9c4edv(dm8fHmlvx6%n z0OqL{dS3NxAz#n|3$qWM{?qKB2x?(uJ+)4owrj{c2^Y7o z{`j#1@d$?T_-w^KYywsn7BP{?o zn2pypHa-^2M2DUI=+i;g#39{<8|IYD+%rG5o^2H;qPBecUY&dl^l3=rQ5ThtceTB%Iui81vSc@qM zUMf10DMjy|A((P$W7&PO+S8AOQ{UIcbokJ0XQzwpfW!H`AK!me$F?e%DOch_Tg641 zFHDTD-`+IDmX`-tplM4J;4P5T@_prC6owTE=0gIO-$q3@2Q>BcC~f}y8PdV%DpC~= zjUCVtGu(a|8E>xg9%+KbGAuq=!6j`g0@Dpcx_xG6Ne4+k`*Rp8BUR!sm7Ve`O$bOb zJ1%yRhQ_IC;n}InnzM#;-8Tji$Z}iqw*m@GY`8|xFn5{;zirq^LHR&GyUcwzn3jpZ zR+lmVvNRkl-`s!o9zX9XyezQtebA+s6f9BCa)!K7RrP#Hy`4S%OBZlzj-#UO?d%Gq zRDno}MiN~SYC>~W*nYXNSeziMqqBamN^i?Ow%KvJ9J`nZkE- z!bysI`0y6A+*>7VZ(kso}`V;$_)zi>1%?BCbNM89Fm_N!JQeYZ7}* zFu!4saj$jx?LB4J6dqH14w!pHV79l&{SMjhXfjUG@crrvQ6tyqmWEp$X2^?@z=E?~ ze2ijPOiG~}pvp3gLwiXW2Jw!lrtZV;uxASo_P6(PvR>NNX8_>pn57pk9P%6w5yAm8 zRU14eUx*Z?XW%R$%g*rolTZ+O5NHPG`SnqyZ))+Vqkz5lSd}bWp;f_}Fr#VYJ2iRW zf35O*>r&ql&|aO~`CBOq4u}zE-T)LwjsER^HWLo@rv$Mw z4m;9b&dE5>1S3A)AEr#Ce(z6}J11wVNDj-VdDI&mU3?L7-{m7LO(-E-yyWmoJwA@8 z!%>k_nxCVUCmwlV{uHh+L6^dE;Ef1&I`}~RIgk5Qy_{}W%(?3UO0sV$meR{y^>_e) zv7^bVQ76xwbNc*^R!<^yT5%S&duQOC9{_bwGd4oy5Fpy)Ajs1?g~_!S`g&V6)xXFV z`)#wO;QFQ8bT$P`C7ZG~JJF{JAp=W+ryia&HbREOQ7{vSbyM)vrBN%{7)KCV2Em?G_gHy%L)8cZgGO3vQK6*i!arsIbW#{tprAJZ}tSd8*8#;{7 zC*ciyZuDEPNy0ZvwMvy?hbqUXe%C|W%Fy0%vO2hN7Z!d>2-D$X3l3M zK#7p;15H_48pUP%61HC+k@ECAignw8sOgZsR;g3_LP5psuM=4|C9J zQsV0%s7C+kY;MZihel9F_*P6eoghkmN9~qOx*?X0E^Ia=n&v38DTq9zp$8^l5e($`55tmu`jzPy@c>|e z|AfUnuDi4|KcBI5K==_WF9oCDzmO4KKkS1Lgn5tGb5Jb-Nt91L+m_60!};s-ceDDY zpW9`VNyl~SdU*=*WwZ3s&!r8Dxb6m%#-3#!zW=RE0`PeptW{lT2PPX4Ze3rLy3hTR@D z?xa;rwW3)%#I6QqB8UN;k|Qj?iPz`OV|2>J#Byt*T8%7%Bm@&x2ycT4R2*30mnNGW zRq5;Cf#e7CalT>RRDp&ka&v1o>#~!t)dMnIhAgpTIh-HB@yS=Kfy{4OFI<3#^th^_% zWy1#ZBO(@F+XLFvfv&D?)9L4IowJZ7$Wag3Ayl1-tr`S+{)Rg%D7Vq^Lxiinc zUt)yi8)YjYUD*}9By<;VF01Uj*nUZh1@n3Pg#REq!D5cgCo6z@`GXoT4RWTyTRC}JJ< zIyk(G6n4Wh+JxCt+seXvvV+DDNZ9dy%Y}Y{$c3(1%V@H6Gf48MvP#r_-FX|sEqN8- zR+$euCJ(3}loq*m@|Ut@Y2HJMbY@r*zksmw5IgpN z2s22%Om)d}Yy1@7Z*)k#3LPAlFRR6eqd-ANE4R`aWaurL9FfoTR*Bg`GQq*6XlM$X zGWx5I)x5D<*LuUyR)?lEw(jCx;n!LWQOwCqtt??d!DBRX%- zXn!r~)Al#_NOI#M3cPCeAO9B;Ri| zId#ER3k)7a^#4kS;wuOrgu$CUZk0c+-@SKu%lRqZfq9Mk@pUXDy+Xr8ip6rDYMf#= zf-Hx2$$g6xet7ymZr*HU&FNh&ZV7iv@BwLobmj9)Yi-C_U`0v@yLaJp;H}zJUtz)W zBK?I?4QFE6DGj%9C6GeCdJ=^qw9CVsbc7>ju zho@rihYGx|CL<%uiD%Pb)LHR7ASZ-N4}D46k1uKzOgT`K7XAxZ457l@(V^XPW$91msMrf!m^q*8Zm zN-V3hssC~%zAz`Z>FjT}tFC!GY1FU_+N~w8GwXCf!YTgamSQq()G zYm6>M940s&-6-Q}b}nN-`37l$-L0{a=G26>0#Yjyd^j@I3h*+V^u5l`w_fJ@3_rFX zA3kFk{&c>qbh7@@am1zOTVD*-=g1h95M2F|yPB*Xj#^IYkv%D55Q;CNpF=+%y!gKEENeEwZJIw$|a_zdu+eAc`+59ZjhX{dC z%o?~->FAo3=Hx2bBF$EsT&m_fnTt8v2$}c_lbOkF(0Vrc!#0fErK$`3$=Q+WJsL%Q zA&WSPuf>CVbVOrHs5!*n2sg>p(&iP>JcF0I8umb3--fs+KofkO0~aje+s5mG#&zP( zOaZ1ka8+j&Xk*!btKJ$6Wo-#=Zd)%;!HpK&#By>ENtX#Jrn5MkmL7{~xaU=~B)+ma z=NE!bptHE*ao{P6ts z`Lu+GcjIDLGh%~6sGN{7R7LdY%giT6lmh; z36zqWTJtN^*|JNkjYV`P&%{&6j#;iq9)ZAx#sAbN`|(0R&^@Sh>mjsN_+_dzmxMwV z2$nBd%cF3&6?H8z?iRI=0Lt$CWCGubm0w3MXA8c|f^mNQMGKp~V{O$Lez=ZY)o-uv zAqI&|^VfuOP4i6`p*385lSX!&asWXUmM_xrb*#kF;g2H^x&T=Lo<#p$jdtHutG(06 zMh{CPt6wQt36t>4)b;P+n$Jq7q_F<4CJy1|y0!Gt)(!3>8xbEGS#^0L=3XRDJjggN zhz1NO^tPC#RqxYLnjjE^pEZuZI=w>3mr8zSBDUI>jNutWN~o>LhOk{1h-I zjj-iD*y>v_QY&d`?8>F>sp=z?Ms^sBC&Q3f(K)oET#l236Gy@x*rJ?F8gv%)33QrE zpQ6@xV{h@2OugPchtw)Gw3zw^{J8to)fFDE81QuKK2DAwK0a!sDJkUXJ=)eng=g48 zg&k-%H`$@7RDy!gh>!<#pD-*-U_=oSvFxi|T5`&~G93D35!I33ZYP86cepTQXwT1i z(WN52)EEt*^UBAANqy@64V6FBawWIu;Oo7!+`K%WkrKFEm!se5!P!q)8WNwb54N=N zXBxc2oa0f*qQ>@bF>5IGKbZ(@m?Aph+Ubn^6!`lM!h6|kwJTEo*zP&?k}?a7%9aA= z6RS5EGwkLoR-O#n5^Js8LKmcKt=^lJ>8MhZj!S)ec|dNQuMYzv5;`vMvy=1 z)<$4%f>lE7{i(lDJM-NsX~slwe^mSW(R-Q_kGhsMe4T_m zJ^=VvNadeiscc9k=OAg}GV{)gpiGWq2jMC-#+i)!7X;Er?3XNBLf$8;KWz|@pTr{Z zt$u~Sf&P<1z=jo90lbJKc__*IX#KV?XobOBli~F2Y-EVqesieinEZ>e{61BV7%bT% z1XaB4XEav%cNSRRIki|V{KE^=qF9+tG}c@`c~;X7ZVJOyB}>Te3Q_!urU*d(9z@(; zi~T_#qXUkT9J;5mF-p|5?oxsC(?zLQ%m>m8m&c#8Sk}K{<+0tqSYLaOoD)$(I`(gv zn4SOxjJPHmUHnrNkQFurPXXsKh7}>zl*T+50U|k7GD1z@KpP=4X>2pM&7Bt60(n<&|;TMG{i)i0+;bH9<~su zo#Hyg^yoZ@>T(LdqbjWqh1mcYs+@B~64NgJH_jqW;of5|i}>&*Pd1jsK6+!;0Pb6w z5@tCJ8>dwMI@oM&OYQPmQnA=r1BkBuDc9;(db*OrS{f+p#lD{fgfvGL2c(uh$x!uC z7Gsk3(i9*3K>-a{V{>AndZ9~F+jxqHq+e^-t$Y=II~U3=*8y-eunX;;Sw4zLl&C_l ztr|jaTx-bJj}n3@4~HrEQ$?7sC}rml8!-a_5=ICVwLkEcS(nlS_~ zzV8c*sVChQWMi=6x=rUmuQ7L4IwRdp$|FMe}WL~Y1*Sk zMF=Cu3%Z>?*2)<2V$d9wp9{o$2Z}*l;^y8(eH|oYvMNSS?k?rRkeib!Xv{4lfRR$@ zt+Inzs-i^!nD^kG&MGsd{_vGRnb{7>`J%ztzlvD#O}=BHeN+E-P6Sqxbypde840Jr zWT}dbiua?YRI~PHq<7OYRT0MYh!XmNFJL9sxPjI>la}xbVRLYPWuM*l#=O@vGn6{O*!Ds z(&l(krv_CwJ`+jSjK#mw(+{Dcsl>IAsg!t_n4?akxsmoH`zLWXK;*=2EVdgLlf-;7 zJgxYMo{o+YM)2Lf^DZeCfGs`w!RRRuz(g^f^Nf4|+B``-js((lR1ms=usH1Q`9kk$ zwnxtQ{cDCqt55DSpO*ID^Deovz_cBiZJb)5t~x&K2`h9cuORI@b5dX9Ro(%g%$Q(E z3w)wBG`*W(l%$ey$ye#!jssj+9gz3PN}2+}lw*~}`s4Nj7|aYIz{qfso46rC^N*bL zvjZUb>yL#;HcV8Z8SnqqWI+d&{;%*my4Xn3!(P)~2SbaP*Tbw6sjAVr!WI*$CBKH~ zGQliGACBno1_|Gi9v8ny7JN?k3(R%(3#!MLSh#S0HY7%D#@O;sP{k}X@U`s>X9)rB ziI$4qr<_zi?%NP!($W_jqz}U)i@mU!vv&2OjnBzbVwVn z`_#(N1_DtJgT{92)J*#}<^8eH&VHL#mF?AdAx(U4g_C2Z7GeOcPt<#) zHXOuWw%<{egmI?W7qh33wxemnvGPou?^tG4Lkq>75$2~{8Ti+9!IU|ludmLyM|F;j zwF21@@5)rxbaId;P5QnBUOy1np*igHB zdeJwePhQNg`R0&bz{d}LK^a~;vEw{1E;vZ%WU6^*#wSen<~N2ILdie6jV2pOT>8}p z!fD>V7cF*uzwZ=GFc!g-4u;I>oA&lsWTRnISs|sLD|F*DsOD%&gye#k6F9wx`O#V5 zBb!;iNuNKR5f#Z%SC5a;B#43jiH|uWjJ18E+=Hdewi_3oGTln~0o{_h=Im`hzQ%13 z;0*)HBqVWJrP`J_&;mu#fI_z(3*bBI)?)EVo_II zh!udO61y;rVW*?p71Qeq)H&g2Sa&v32gj{J%HcmQEwK5~Tn&Z^UtaFK2l|&E-;24xEgDVp}x@-P& zU+4WOu*1Qkc1sCM=egu($o-udE2re~r&E18IvIaifroovdN9$^J;(+B#M6xuN(IJT zT4*o!w{Sc@@}o|82nJZ-{R|7z?E_qo-xo7_`hjq(-+5cIj3c@E*yLJS30k>DFOnr5f#&3>_@_QOfw{Kpd zCnfRQHHHC_z5zgE0Wg2qKR%3?GEi46wYRn9tO7-mg%l8PQ`gt8Oq=EYC{8SO$C`#J zeE8l?du5vV>={&eCh|Ek^K627BtJOyOKDf9;W8`tM|MIt7N0w=uu&pg7F+d@lP3fF zT>t4l*uLlxwGb9psVjLXeUNI~4dBGp19pk71G7^JkSt8wF~ZcSo3F5xPMJ5+x~4{6 zTiq5Nq7k90hikq<`zqibJB4HMwB}skk&0&r9USzjNHZ?l_Fq;|k3llZYisN>!_mtW z5c_c!SIeG#4nsPK(?9qJif-})IvtZx!)3*nt2E|f==wQ%68tG2BXPZ~eJCRTJAOEV zC*3&GG=CG_pb3JU6tO?!<=8o7nHbA*!E1gl$P9xYCUcWXu?rI*1k@A6R3}~~{By&9 zN|^KODH}MX$#H-mjMD!sHMq!|iKYe>)_4LOcGbc>u&b=jzgy~FlL5GT*s()LUiDTR zK?_?EzN!@IxfZm^lJkwK$q8pQ9}=YsMi!i?*dM6;XCNS9ih(W)AJD-jXa60-d(BJV zl&Ro7F!wi;0~&(=KdH?Tu2pHhJqIOT_3J+NZ_@v#%p`~yTJ9=J+t2gjMit)G$awGw zjZ5M0JwEqOwlfC@(Ml!gd!DOK0tC*XeEvb?^gy*8(MOE?UNPV1pVnx@7GTWJqoa!= zd8Yx{%}~Sk6S<4VPo;rv!iGW223Y?u=}n(bN#IH3Wmbq20U*9{*7yk?rBp`>((JGR z_oWRf>?EUSW#x7}Dk=)OZ9iEviiq73qr?7Kql^x*{U-(DMaY{LSuXp_HBy$uc=3wT z4)Sm!Ve(bU(im{nWL>B(XYTFkWT*Mfwx&I=xlG_iP1>WtxVTA~+pay>i>07HpRsgf zcXB%T?at*vk<+*2fPIko4J*iN8r>(b{CKcl+eI-}bVX624Au@444_a?F#dmaETl&Z zti5KQ1v_^<3%IhMbcB}4OnvHdPg}m1^#E!jy7nbb z3A|)m)H$w4;6R+4Vo7NxT)AaeXf6A{N=pH?-grSEy`UqV%OzQ^l zWfXx)NWovr!0i`ZYM+8`!~u=XoC3p=POuPuFwmP@m#tiW_{8PA8;$IC=GHZpgMaAyozLu{K- z^1xib8bH00U)3M3NI{F_q${VomxTiArGcqE*OG4%l1I;i`hz>EiG|lZCaXdQWUf#0 zd)Hz&yYjK`BP=NY*5@e7eDM`N>fB_miA+WSDaX~qud}um^QF99+T08r^qCrxs(<>b zaHJOfH=buDT_$}ty-Bh(4);cdA$PEBN@ib zeILj^-G^$s$K6#g-Iq+t%m?m;U#7mSsg8H9Ll3`CLyQ_~5|GaTxOMy*J*P(h&bwe4 z6TRBq>}F-YwYRQZ9NT6EBtc3X{Z}V6brmEkDtZ(AruUbrfw$ovLD!dW+aVL76E3Pp z4S72!T!gy>_O&=@JPs1cpEKGre$xs#ePp`Js=T$ z?diNQ2N0YI$hj5KHW{9*FJ8+m6Q(9c#ah1jP?}9a=J$$!6@mj$*7vxWp>K2#20%~B zM0z`iT37)_VZ5a}YcSTzX@5ObQ4_$f_unSIV7N_6S!(4GaZ;PzMJ^q5#xZIy`-ubO0cc0crNtWPABF6?(P(?d4qViXc{ktNAhY;Hy-g`lx_JxXvLA+#P~1y!YzS=rKInVt)G&O= zqd?Ob5kg2lo!y}IagPd>gmrML^;7za+!emn`70b)XV)3xe;kkzYZ6r z^0*#-$ZhMbxIp=AcP=Pq@kXXUdaM=C3WjTpC!H;c`U7CX_ceWvSc34k9i*RES)h)Z zitjtVn%MFU_|}tfKRN^Ga!VgRFYW!MZjT6Q9Jt{_3?+azlFV z7eVRAHH~ls-1{4C15gILXNfjTpRCoD&Zm9b8_HZ_J}U zd-5+U8zPznLy^lsixa<*YI5CnwD~zgafx*}_Fn2bz#UInEFa7RGJ3O$&_|c77l&~X zBRZXrN-1lk`blQ&suiQ6;W(14!}Aqy&#eCXChG;1$KM-K`RAuX(Y?eLND&8uh(zP` z!)7nGaeF|C`~h)jSNdnMFdcI97EhFJ_0RFIP4l*Ms$uQxCG%uGA*UESTtSLlSGAUZ zvQ0VYaN~D>?smxoJyZQV6 ziiKUg|Fgk08)wD{dmePN*{uJ%gvVXW?=)VV!o=)g0GJnXb>*pSqsPO1iiYPN@;A+n zkRb#VUaJjc{_!F=J{a?V;dMr}dj-I~iJTWCRuJU@zL4M33bZ#t(hio&he{A`8%WKq z)=FuviEB`FOJ7NwxwP%q(YvhIJIswfm(M!<4gxQhf4!TTj~l7z;1??%Hr{0`TXgdJ zMvFV5I9uTS!f|fz=w)!(FB+PM82k$v2LFVlEa@*f+JDz^5sG8=#@0pLgh)E@qdj7qV$@eI;LR=T?fK;uu9|@^w9qLAqGBQM`OxF_(w; zD9P+6jHb=|;!)&C;IaF`(#y4-*0Qr4G8t`Q=c{5nb;09Q!zAzs;qv2}ShUi~``Mjn z+-Tm}2?4pC0Z>?J;RAtYHI9E*#ben0ozTzIYBnMC*f|7&y7%>`2_^Mwy}9}BKbHi` z+gB+z>ZRUB4eRFtw!rzlib6(Zgu98UDzI${n+KS)f#eGp*mrqZn`H38jT!6B}g zaCYnY4MTgyb!kDJ-1MY`z+*xCC~WQMY0El^-8hV1)RnCQXB;co=g7R|y>oo@yc5?6 ztc|eGvVUzlhDsfd`+4lPVc+e32$Ez#NG4tzL&FG!^`0%#G&em8`uy}$IdaxG07LeMz|qma3q>s`A3)3xAk z#&vNWq@b9jF()NyYtuD0_X3x-q~Ar*m@-8Ep;M&F)V2X5x(`p|$~6jJ5fR3_%bTF( zEpeen3qBPC)rTvb#Q&x0PK@luM(nfzkEu>#?$Ax{*o-!o%>z1)kKhgg0#Ul*PyU;e z1LK@5E`+rVG(s6gwi7;UXPU!9GqwECRw@}71}Em+lZ$oDl`btuW^?~xsmCcujKQZd zTrZ8BN*<=I@#zRq=l{Kv&+;!~REj^n^5njA_n5nxPicPfxvU-OY65f>(oMnzRdRV>DEq5*;ANDd*0QAKF z3vRTUM<42!Hy=4)+Ks$AaqAlYKJkz~|K--RoAK|764M|D{jsb(rpFX2{vg+g+Efb< zyBUKT1^S{bC|EM!+WOg>cvwD)G3%dn>T0b&hf~r(Fpg% z*CzS*Kec|+!K{4zGcl8u5}@383_IScflr3BKE~*mT-~#fJJ}9-X1c^t%j;mIH?i1^ zo{3eCMUC$&_i4@?D88T!7rne+XIqz@ooS7g-SkL1)OLX7h+ zc^KUZ3NqrbIlmSC4;biB+aOgd@}!lzFeo^A9TjHdBml6&O~3$H@u%!azCTMp+I_gs z7M8Ay$U4Ww75z4s#aK%!S<>+D`2k)$u=o0J%M!|byeD1QO$vXg?ne@JUJS{|q|kq6 zJU91@KpggdD%B*{d&tkc4d5h4Es3S%UEGs9%kRLeFbDIo$~?u6ecJ`imeH5d!#Y+K zp=s7m93o(G5|~02a;$I42GH+{9nXQ!9f2`9v(~e9v+`0!89r%OvWWb#)Q)5FObf~J z0vC-caaBd|1>4x_iU(Z5=9;M)H;%GC6PipM8yIw<6kE>(UaNSF|jV4ZzX)sxn1n$5I06(sdgUG8-&Mx4gkx*yn(Aov;4Q()YVTA zBd&};F1{FSiV5%YTqsr9?}zsu%4h?#UQ8j+-bVPXBLIL32&OCn*ynt!8pNi7TuVNL zisV(>xmz|UgLnG?UtQ)TVMS%g?#Tj?0RR^u3f6>V$`zAMS2TQ-qbnFH9On3R_;TKc zb2QSZ_K+S~Iv6NdjP}N{Gh=U1QZwPHuTU=e{JSg_5%Q!VN*KetgyxdsCg%oWnn*8D zsRl*z@43T^?c#|Q6=DnkvIWKu9PDbiJSL6eJSUClKl|)lDsjqnbn+$BC96%Y%Tq$2 zNrQ~Zg!fa0evgtZIUW!#Sgo`t6YRA9>$hP*-b#=wt1;Mnw2?;JMCOzJ>@R~`q#xu! zq$zW9kyq7O+#5>8@dg^c_y(#Nsz8XpQO$Kdn;ZS;wRylNjcEjfzlg*mJcSo;fjLeG zz&+Tkcwo}x+MA(>5|#gXd+bLZhJ5BOHLXm}Ynj#9X|(V{cJNLbU1)NfJ_XbkS2lfNxWJk%Hh1dZ8x{=^k|f#Z7A}W<}T~%D=gm z8jSeGr1AH%G-SdP$1>(tR7?hKXDMM?*!O%>X1ddu$nc1U22HHTE2tov%@~d$b%dv* zXMb&~7A5sN`L3=pVCYd@Vvq@PKOzl@|I4*?%tn%f)O{_-PkxnwuR|ROOqnM&fy*f} zmC!XuLk1sTxnMZmRNW+`^B!V1?A9^;@Z4=)Oh(4OS__i5CS;QTJJfSqK>;`?@mju; z#Ip%B4+^eg@}B*R2Oog;VXlQaG>jz{$No%u!3wZ9o*;(Yfk$BAEoKPtrAF>Uq9_62 zQL1B*`oBUxKZ)9ANo;$qbrb!}Gc2i_lqJecKgu9{#O120L|# zY7HFPE9o1QW=`?$cDoK_T8{ja;XzK-iIc3`2d1Xs2#t9$*Kbj;X?0`)`k_w%!OQ(E zH7!kL_8}Y*tSXzFx0`1YqV=3om4IT0jG*C$@!ylXbxJ_xl)SMk&!{T0v3l4X&yx9Q zq$|;V$z~^#h^OwUj6ODCh$)cZXT*Fs(27Cj%p@3@g#j?T1ms-0UGPZbWq4n zCBt$E6)M4(QlQ?jt69N9^{BU@mL@i4dM>99jQB*jSUItG>AbCbLoCmmw+f!*U4A{J zrjz*h?yQP|VsubjNXcq3Vtqvb;{uz;q359{(82LlOZ>kc>7DA9{X+Qv8mqIlhm4`> zVbI$4P$vdRR(e@9a?22ZMEf7@_iw6ea8QRRRP$WD-XizS1A1?!KvBgGqkB5*9-eOb zxA^Wop;-OM_SV+I_I|PZi8_D_1I8YKfja>2tS9_GfxHA{v)$h|@gCv-{{k0D1Wyc~ z1_R*QX?%c@^Td0ks8fIp2i*|2pMyXm5??j+zF*-ome{Z@)xN1WzW5Mx8nUwl0H)qr zl(0z5OYsz*Y&!MqcWl28f-0-KU9z5XAx;VL{Yp-VPU}kX0M$#xH6e8rx5-DqvbIVe<%` zfuKEvW3+s@>iuoRo{0y~3rRD8iygv7bXNTM@dF!EKTvvhqS~4%$ybhEDH`C(U7Ebi=E3EnbfK|800|9HM*y~+^vpk8M z*px@ct&cIhQ6_@@m?1!=f^DUTyy&~6yWu}(SIe}o=|0H3-5@b(H_We!LhYHtnMy?g zv#*?fBHP8B9x}=BUj_rtq6bgkxB`#zt3WY+UQ1S?wibA1K fD3ly}{`~&~-jxT6JqaIA00000NkvXXu0mjfL2OEl literal 0 HcmV?d00001 diff --git a/packages-microsoft-prod.deb b/packages-microsoft-prod.deb new file mode 100644 index 0000000000000000000000000000000000000000..4f7f63d330c002a70a03c99fab6f7d6131c26486 GIT binary patch literal 2452 zcmai$c{CIX7r-saItF2=j8P~ljUlqk$XLdbEh5as*oMeHHFgo9C}A|yWC@Y7j4j#M z5;N8qlD$`!vG1Pc>%H&2zu)iNbMLw5o_qiMopYr$vHs5P2p$(#Cl9RmB_|JWtbY&$ z0#Ur8sCX5o3{`_cAkg3azgZCqRZ&)kKz_$RIP$<0pghh#-huu;UJ8L&e+69dfA6cR z{CmNXNAfWj%U`guFQ#KWN1?*80YwgeLrZSa!^FmcBq|cibTXeEZvUjH#CZqLmo|rm#I5KYXH>{B;eOW4A~= zuGbq#*HL2Z48K_LdL+o*?9h+Y)jv{kiYavcWaoDDkp44!rH{dIO|k1@)b8zwoI#uI-_-RqjpQ-P}nFvJJK4 zL}W~#f|p`qwz(!#Ov@QSUd1>_WtLSnlkP-0znAum{uvebWc79DLT8#QX|Q8H6xmf% zaKbuO@xXJUP=irM`UVP(^4)b1uN`A=s@XStJl$|uesy~ku+ zw??FhVN$5x-P}9C$m4xirBGzEJUwsFH}Q}5th+Q9&#~w|7Sl`jmK*^SFA$c;$4Rd6 zYO#6AVhksmK9OS96~d9Q`nJw@y#ED7y{FavI&ExmM%Oqm53c3Q>G4inO~190NyH?n z^LnojmD#!vscIK(Y%X&x@##-?ebu9OJ*~IZavgAB-#gEFQUlKr4{AGAYR&g%>^0et zU(yV*rLD9wMVnbAXo8=9{->3FxRDc{6=8Sd3-nwAM{m$1PxTT6YP~Fsruwgrz4=K4FR&7HoJH!sJjft6UnYc9UYYJxl(IZ|&VZG1hrI~gH=&xCn7No|jd`>BshWOo%r z#!Z69HA0~YV)c9t$OZDtb_H8^B8hk~+AW3pa=n_6;a3|GR3JDVfq1Z8T9_^g%BGnH zk#kwwML7E!P}5-E7fe!R1ovo()w?CC>`Ol!)8mj|w4G;}p9WHMm3{PB@S67uS5UR+ z5i$Ktf9Jt_`4%0YbRd^gLx*Anpi)C=6=PltC3(cr+$ILIH?2o7Co{g{2z?I)X#8ZT zwKRcUW8sa8-_`vGZxfT;cTR}fhn$44_XL2DJ!!x8)DdRS&Lv)(+j2-HV2lK^Xbfh} zRmQqT@%a($g^PGY@3GeAAYgOf&ZgNe0{86umOVE{Hx7Q384v;t!y8L-7UGKFMX-Wy zWCjXfAJhaq32b69+Qk?&7i~i|pLUF= zPPIOM?$5?Jj_l|3KW$8VYto3?3BT5idL-I7Z1}Uh7Sxv9MMr;ZHRS@s^hG}J2<^W? z!oacvlau-HrIShRj#Cnf$rUI$6em582Z7eo=lgNLPke{?p^h_jSfp;7OrefbPG4X6 zltL&A%met`VlpViJ`V5gad8!U0>xQ{4vZlibO}?{XaOf=zhu7@$+KE3P|y`Nz0kvG z>5iHDDmjuEEt977adMpi0Uo>aqZu=(i`?*SNi|tfB0X20?tzC4H`SS#cZaaI;rJLs ziDnU|nXA-?sc3Ai^YUdG+!>N|jJF1;Pj-EJd>Li%ohdZblEJTO{*$w_Wau;QY>cJO zWL^U1x@>OG$}P0i&700#ynwcdcc&GI_U9W6lU;acD(mhm#6IPpx;HH~dmH(-ioh{^ znn&G@+Cnw5iSoT?oeF(|WFQv26brb5Dl+zs!6ufMf|H1>GmjIjqZ?uX>Q(I*<4+L~ zX&-a6oIE49ELWACXa?I^fih6cZP!JDxE_@2sAl3K=V>r|RLO4Yt0Hoyi`y%!0o@94 z?hAWB&(@TT?<_+yD5Remx7UxPu9Lfxa|ie zDt^@#l(>i1N=*aj +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Common +{ + using System; + + /// + /// Represents duration with the nanoseconds precition. + /// + public sealed class Duration : IComparable + { + private const long MaxSeconds = 315576000000L; + private const int MaxNanos = 999999999; + private static readonly Duration Zero = new Duration(0, 0); + + private Duration(long seconds, int nanos) + { + this.Seconds = seconds; + this.Nanos = nanos; + } + + /// + /// Gets the number of second in duration. + /// + public long Seconds { get; } + + /// + /// Gets the number of nanoseconds in duration. + /// + public int Nanos { get; } + + /// + /// Creates a new instance of class. + /// + /// Total seconds. + /// Nanoseconds part of a duration up to 999999999. + /// New instance of class. + public static Duration Create(long seconds, int nanos) + { + if (seconds < -MaxSeconds || seconds > MaxSeconds) + { + return Zero; + } + + if (nanos < -MaxNanos || nanos > MaxNanos) + { + return Zero; + } + + if ((seconds < 0 && nanos > 0) || (seconds > 0 && nanos < 0)) + { + return Zero; + } + + return new Duration(seconds, nanos); + } + + /// + /// Creates a new instance of class. + /// + /// Duration as TimeStamp. + /// New instance of class. + public static Duration Create(TimeSpan duration) + { + var seconds = duration.Ticks / TimeSpan.TicksPerSecond; + int nanoseconds = (int)(duration.Ticks % TimeSpan.TicksPerSecond) * 100; + return Create(seconds, nanoseconds); + } + + /// + /// Compares durations. + /// + /// instasnce to compare to. + /// Zero if equal, -1 when lesser and +1 when greater than given value. + public int CompareTo(Duration other) + { + int cmp = (this.Seconds < other.Seconds) ? -1 : ((this.Seconds > other.Seconds) ? 1 : 0); + if (cmp != 0) + { + return cmp; + } + + return (this.Nanos < other.Nanos) ? -1 : ((this.Nanos > other.Nanos) ? 1 : 0); + } + + /// + public override string ToString() + { + return "Duration{" + + "seconds=" + this.Seconds + ", " + + "nanos=" + this.Nanos + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Duration that) + { + return (this.Seconds == that.Seconds) + && (this.Nanos == that.Nanos); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (this.Seconds >> 32) ^ this.Seconds; + h *= 1000003; + h ^= this.Nanos; + return (int)h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Common/IScope.cs b/src/OpenCensus.Abstractions/Common/IScope.cs new file mode 100644 index 000000000..fa98c29bb --- /dev/null +++ b/src/OpenCensus.Abstractions/Common/IScope.cs @@ -0,0 +1,28 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Common +{ + using System; + + /// + /// Scope marker. Used as a syntactic sugar in methods like StartScopedSpan so it can be + /// wrapped in "using" block with automatic scope completion. + /// + public interface IScope : IDisposable + { + } +} diff --git a/src/OpenCensus.Abstractions/Common/Timer.cs b/src/OpenCensus.Abstractions/Common/Timer.cs new file mode 100644 index 000000000..52acc48f1 --- /dev/null +++ b/src/OpenCensus.Abstractions/Common/Timer.cs @@ -0,0 +1,81 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Internal +{ + using System; + using System.Diagnostics; + + /// + /// Gets the current time based on start time and precise duration. + /// + public sealed class Timer + { + private readonly DateTimeOffset timestamp; + private readonly Func stopwatch; + + private Timer(DateTimeOffset timestamp, Func watch) + { + this.timestamp = timestamp; + this.stopwatch = watch; + } + + /// + /// Gets the time of creation of this timer. + /// + /// Time of this timer creation. + public DateTimeOffset StartTime + { + get + { + return this.timestamp; + } + } + + /// + /// Gets the current timestamp based on start time and high precision elapsed time. + /// + /// Current timestamp based on start time and high precision elapsed time. + public DateTimeOffset Now + { + get + { + return this.timestamp.Add(this.stopwatch()); + } + } + + /// + /// Creates a new instance of a timer. + /// + /// New insance of a timer. + public static Timer StartNew() + { + var stopwatch = Stopwatch.StartNew(); + return new Timer(DateTimeOffset.Now, () => stopwatch.Elapsed); + } + + /// + /// Creates a new instance of a timer with the given start time. Used for test purposes. + /// + /// Start time to use in this timer. + /// Stopwatch to use. Should be started. + /// New instance of a timer. + public static Timer StartNew(DateTimeOffset time, Func watch) + { + return new Timer(time, watch); + } + } +} diff --git a/src/OpenCensus.Abstractions/Common/Timestamp.cs b/src/OpenCensus.Abstractions/Common/Timestamp.cs new file mode 100644 index 000000000..9a3dbf116 --- /dev/null +++ b/src/OpenCensus.Abstractions/Common/Timestamp.cs @@ -0,0 +1,229 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Common +{ + using System; + + /// + /// Timestamp with the nanoseconds precision. + /// + public sealed class Timestamp : IComparable, IComparable + { + /// + /// Represents zero timestamp. + /// + public static readonly Timestamp Zero = new Timestamp(0, 0); + + private const long MaxSeconds = 315576000000L; + private const int MaxNanos = 999999999; + private const long MillisPerSecond = 1000L; + private const long NanosPerMilli = 1000 * 1000; + private const long NanosPerSecond = NanosPerMilli * MillisPerSecond; + + internal Timestamp(long seconds, int nanos) + { + this.Seconds = seconds; + this.Nanos = nanos; + } + + /// + /// Gets the number of seconds since the Unix Epoch represented by this timestamp. + /// + public long Seconds { get; } + + /// + /// Gets the the number of nanoseconds after the number of seconds since the Unix Epoch represented + /// by this timestamp. + /// + public int Nanos { get; } + + /// + /// Creates an instance of class with the given seconds and nanoseconds values. + /// + /// Total number of seconds since the Unix Epoch represented by this . + /// The number of nanoseconds after the number of seconds since the Unix Epoch represented by this . + /// New instance of . + public static Timestamp Create(long seconds, int nanos) + { + // TODO: + if (seconds < -MaxSeconds || seconds > MaxSeconds) + { + return new Timestamp(0, 0); + } + + if (nanos < 0 || nanos > MaxNanos) + { + return new Timestamp(0, 0); + } + + return new Timestamp(seconds, nanos); + } + + /// + /// Creates an instance of class with the given total milliseconds since Unix Epoch. + /// + /// Total number of milliseconds since the Unix Epoch represented by this . + /// New instance of . + public static Timestamp FromMillis(long millis) + { + Timestamp zero = new Timestamp(0, 0); + long nanos = millis * NanosPerMilli; + return zero.Plus(0, nanos); + } + + /// + /// Creates an instance of class with the given time as . + /// + /// Time to convert to . + /// New instance of . + public static Timestamp FromDateTimeOffset(DateTimeOffset time) + { + long seconds = 0; +#if NET45 + var unixZero = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + seconds = (int)Math.Floor(time.Subtract(unixZero).TotalSeconds); +#else + seconds = time.ToUnixTimeSeconds(); +#endif + + int nanos = (int)time.Subtract(new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).Subtract(TimeSpan.FromSeconds(seconds)).Ticks * 100; + return Timestamp.Create(seconds, nanos); + } + + /// + /// Adds duration to the timestamp. + /// + /// Duration to add to the timestamp. + /// Returns the timestamp with added duration. + public Timestamp AddDuration(Duration duration) + { + return this.Plus(duration.Seconds, duration.Nanos); + } + + /// + /// Adds nanosToAdd nanosecond to the current timestamp. + /// + /// Number of nanoseconds to add. + /// Returns the timstemp with added nanoseconds. + public Timestamp AddNanos(long nanosToAdd) + { + return this.Plus(0, nanosToAdd); + } + + /// + /// Substructs timestamp from the current timestamp. Typically to calculate duration. + /// + /// Timestamp to substruct. + /// Returns the timestamp with the substructed duration. + public Duration SubtractTimestamp(Timestamp timestamp) + { + long durationSeconds = this.Seconds - timestamp.Seconds; + int durationNanos = this.Nanos - timestamp.Nanos; + if (durationSeconds < 0 && durationNanos > 0) + { + durationSeconds += 1; + durationNanos = (int)(durationNanos - NanosPerSecond); + } + else if (durationSeconds > 0 && durationNanos < 0) + { + durationSeconds -= 1; + durationNanos = (int)(durationNanos + NanosPerSecond); + } + + return Duration.Create(durationSeconds, durationNanos); + } + + /// + public int CompareTo(Timestamp other) + { + int cmp = (this.Seconds < other.Seconds) ? -1 : ((this.Seconds > other.Seconds) ? 1 : 0); + if (cmp != 0) + { + return cmp; + } + + return (this.Nanos < other.Nanos) ? -1 : ((this.Nanos > other.Nanos) ? 1 : 0); + } + + /// + public int CompareTo(object obj) + { + if (obj is Timestamp timestamp) + { + return this.CompareTo(timestamp); + } + else + { + throw new InvalidOperationException(); + } + } + + /// + public override string ToString() + { + return "Timestamp{" + + "seconds=" + this.Seconds + ", " + + "nanos=" + this.Nanos + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o is Timestamp that) + { + return (this.Seconds == that.Seconds) + && (this.Nanos == that.Nanos); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (this.Seconds >> 32) ^ this.Seconds; + h *= 1000003; + h ^= this.Nanos; + return (int)h; + } + + private static Timestamp OfSecond(long seconds, long nanoAdjustment) + { + long floor = (long)Math.Floor((double)nanoAdjustment / NanosPerSecond); + long secs = seconds + floor; + long nos = nanoAdjustment - (floor * NanosPerSecond); + return Create(secs, (int)nos); + } + + private Timestamp Plus(long secondsToAdd, long nanosToAdd) + { + if ((secondsToAdd | nanosToAdd) == 0) + { + return this; + } + + long sec = this.Seconds + secondsToAdd; + long nanoSeconds = Math.DivRem(nanosToAdd, NanosPerSecond, out long nanosSpill); + sec += nanoSeconds; + long nanoAdjustment = this.Nanos + nanosSpill; + return OfSecond(sec, nanoAdjustment); + } + } +} diff --git a/src/OpenCensus.Abstractions/Internal/IEventQueue.cs b/src/OpenCensus.Abstractions/Internal/IEventQueue.cs new file mode 100644 index 000000000..0968285c4 --- /dev/null +++ b/src/OpenCensus.Abstractions/Internal/IEventQueue.cs @@ -0,0 +1,30 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Internal +{ + /// + /// Event queue abstraction. + /// + public interface IEventQueue + { + /// + /// Queues a single entry. + /// + /// Entry to be queued. + void Enqueue(IEventQueueEntry entry); + } +} diff --git a/src/OpenCensus.Abstractions/Internal/IEventQueueEntry.cs b/src/OpenCensus.Abstractions/Internal/IEventQueueEntry.cs new file mode 100644 index 000000000..ae870a0db --- /dev/null +++ b/src/OpenCensus.Abstractions/Internal/IEventQueueEntry.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Internal +{ + /// + /// Queue entry abstraction. + /// + public interface IEventQueueEntry + { + /// + /// Method process will be called when queue entry needs to be processed. + /// + void Process(); + } +} diff --git a/src/OpenCensus.Abstractions/OpenCensus.Abstractions.csproj b/src/OpenCensus.Abstractions/OpenCensus.Abstractions.csproj new file mode 100644 index 000000000..022f55899 --- /dev/null +++ b/src/OpenCensus.Abstractions/OpenCensus.Abstractions.csproj @@ -0,0 +1,35 @@ + + + + net45;netstandard2.0 + netstandard2.0 + OpenCensus .NET API abstractions + True + Tracing;OpenCensus;Management;Monitoring + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + OpenCensus + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenCensus.sln'))/build/OpenCensus.prod.ruleset + + + full + true + + + + All + + + + + + + + + + \ No newline at end of file diff --git a/src/OpenCensus.Abstractions/Properties/AssemblyInfo.cs b/src/OpenCensus.Abstractions/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..25b187d0b --- /dev/null +++ b/src/OpenCensus.Abstractions/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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.Runtime.CompilerServices; + +[assembly: System.CLSCompliant(true)] + +[assembly: InternalsVisibleTo("OpenCensus.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif \ No newline at end of file diff --git a/src/OpenCensus.Abstractions/Resources/IResource.cs b/src/OpenCensus.Abstractions/Resources/IResource.cs new file mode 100644 index 000000000..5dc71e478 --- /dev/null +++ b/src/OpenCensus.Abstractions/Resources/IResource.cs @@ -0,0 +1,40 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Resources +{ + using System.Collections.Generic; + using OpenCensus.Tags; + + /// + /// Represents a resource, which captures identifying information about the entities + /// for which signals(stats or traces) are reported. It further provides a framework for detection + /// of resource information from the environment and progressive population as signals propagate from + /// the core instrumentation library to a backend's exporter. + /// + public interface IResource + { + /// + /// Gets Type identifier for the resource. + /// + string Type { get; } + + /// + /// Gets the map of the labels/tags that describe the resource. + /// + IEnumerable Tags { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ICount.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ICount.cs new file mode 100644 index 000000000..518278f79 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ICount.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Count aggregation. + /// + public interface ICount : IAggregation + { + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ICountData.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ICountData.cs new file mode 100644 index 000000000..a83371068 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ICountData.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Data accumulated by Count aggregation. + /// + public interface ICountData : IAggregationData + { + /// + /// Gets the counter representing the result of an aggregation. + /// + long Count { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/IDistribution.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/IDistribution.cs new file mode 100644 index 000000000..8beb6eb2a --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/IDistribution.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Distribution aggregation. + /// + public interface IDistribution : IAggregation + { + /// + /// Gets the configuration for distribution aggregation - distribution bucket boundaries. + /// + IBucketBoundaries BucketBoundaries { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/IDistributionData.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/IDistributionData.cs new file mode 100644 index 000000000..492ccf95b --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/IDistributionData.cs @@ -0,0 +1,56 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System.Collections.Generic; + + /// + /// Data accumulated by distributed aggregation. + /// + public interface IDistributionData : IAggregationData + { + /// + /// Gets the mean of values. + /// + double Mean { get; } + + /// + /// Gets the number of samples. + /// + long Count { get; } + + /// + /// Gets the minimum of values. + /// + double Min { get; } + + /// + /// Gets the maximum of values. + /// + double Max { get; } + + /// + /// Gets the sum of squares of values. + /// + double SumOfSquaredDeviations { get; } + + /// + /// Gets the counts in buckets. + /// + IReadOnlyList BucketCounts { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValue.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValue.cs new file mode 100644 index 000000000..1d2b70bc2 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValue.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Last value aggregation. + /// + public interface ILastValue : IAggregation + { + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataDouble.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataDouble.cs new file mode 100644 index 000000000..055411d86 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataDouble.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Data accumulated by the last value aggregation amongst double values. + /// + public interface ILastValueDataDouble : IAggregationData + { + /// + /// Gets the last value as a double. + /// + double LastValue { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataLong.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataLong.cs new file mode 100644 index 000000000..e360ff15a --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ILastValueDataLong.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Data accumulated by the last value aggregator amongst long values. + /// + public interface ILastValueDataLong : IAggregationData + { + /// + /// Gets the last value as a long value. + /// + long LastValue { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/IMean.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/IMean.cs new file mode 100644 index 000000000..c2a95f9b4 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/IMean.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Mean aggregation. + /// + public interface IMean : IAggregation + { + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/IMeanData.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/IMeanData.cs new file mode 100644 index 000000000..a65953fe6 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/IMeanData.cs @@ -0,0 +1,44 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Data accumulated by mean value aggregator. + /// + public interface IMeanData : IAggregationData + { + /// + /// Gets the mean value. + /// + double Mean { get; } + + /// + /// Gets the count of samples. + /// + long Count { get; } + + /// + /// Gets the maximum value. + /// + double Max { get; } + + /// + /// Gets the minimum value. + /// + double Min { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ISum.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ISum.cs new file mode 100644 index 000000000..16c728b76 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ISum.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Sum aggregation. + /// + public interface ISum : IAggregation + { + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataDouble.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataDouble.cs new file mode 100644 index 000000000..01a58255b --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataDouble.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Data accumulated by the sum aggregator amongst double values. + /// + public interface ISumDataDouble : IAggregationData + { + /// + /// Gets the sum of values as double. + /// + double Sum { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataLong.cs b/src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataLong.cs new file mode 100644 index 000000000..2d92bfe37 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Aggregations/ISumDataLong.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + /// + /// Data aggregated by sum aggregator amongst long values. + /// + public interface ISumDataLong : IAggregationData + { + /// + /// Gets the sum of values as a long. + /// + long Sum { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IAggregation.cs b/src/OpenCensus.Abstractions/Stats/IAggregation.cs new file mode 100644 index 000000000..a78f24686 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IAggregation.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Aggregations; + + /// + /// Represents the type of aggregation. + /// + public interface IAggregation + { + /// + /// Executed callback specific to aggregation without type casting. + /// + /// Expected return value. + /// Callback to be called by sum aggregation. + /// Callback to be called by count aggregation. + /// Callback to be called by mean aggregation. + /// Callback to be called by distribution aggregation. + /// Callback to be called by last value aggregation. + /// Callback to be called for any other aggregation. + /// The result of the aggregator-specific callback execution. + T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p6); + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IAggregationData.cs b/src/OpenCensus.Abstractions/Stats/IAggregationData.cs new file mode 100644 index 000000000..290961261 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IAggregationData.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Aggregations; + + /// + /// Gets the aggregation data. + /// + public interface IAggregationData + { + /// + /// Executes aggregation data specific callback without type casting. + /// + /// Callback result type. + /// Callback for the double sum data. + /// Callback for the long sum data. + /// Callback for the count data. + /// Callback for the mean data. + /// Callback for the distribution data. + /// Callback for the double last value data. + /// Callback for the long last value data. + /// Callback for any other data. + /// Callback executuion result. + T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction); + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IBucketBoundaries.cs b/src/OpenCensus.Abstractions/Stats/IBucketBoundaries.cs new file mode 100644 index 000000000..fe39fc261 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IBucketBoundaries.cs @@ -0,0 +1,31 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System.Collections.Generic; + + /// + /// Bucket boundaries for the distribution aggregator. + /// + public interface IBucketBoundaries + { + /// + /// Gets the list of boundaries for the distribution aggregator. + /// + IReadOnlyList Boundaries { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IMeasure.cs b/src/OpenCensus.Abstractions/Stats/IMeasure.cs new file mode 100644 index 000000000..5db6098e2 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IMeasure.cs @@ -0,0 +1,55 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Measures; + + /// + /// A single measure to track. + /// + public interface IMeasure + { + /// + /// Gets the name of the measure. + /// + string Name { get; } + + /// + /// Gets the description of the measure. + /// + string Description { get; } + + /// + /// Gets the unit of the measure. + /// + string Unit { get; } + + /// + /// Execute callback with the specific measure type without type casting. + /// + /// The result type of a callback. + /// Callback to be called for the double measure. + /// Callback to be called for the long measure. + /// Callback to be called for any other measure. + /// The result of measure type specific callback execution. + T Match( + Func p0, + Func p1, + Func defaultFunction); + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IMeasureMap.cs b/src/OpenCensus.Abstractions/Stats/IMeasureMap.cs new file mode 100644 index 000000000..cdde048eb --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IMeasureMap.cs @@ -0,0 +1,54 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + + /// + /// Measure map. Holds the mapping of measures and values. + /// + public interface IMeasureMap + { + /// + /// Associates the measure with the given value. Subsequent updates to the same measure will overwrite the previous value. + /// + /// Measure to associate the value with. + /// Value to associate with the measure. + /// Measure map for calls chaining. + IMeasureMap Put(IMeasureDouble measure, double value); + + /// + /// Associates the measure with the given value. Subsequent updates to the same measure will overwrite the previous value. + /// + /// Measure to associate the value with. + /// Value to associate with the measure. + /// Measure map for calls chaining. + IMeasureMap Put(IMeasureLong measure, long value); + + /// + /// Records all of the measures at the same time with the current tag context. + /// + void Record(); + + /// + /// Records all of the measures at the same time with the explicit tag context. + /// + /// Tags to associate with the measure. + void Record(ITagContext tags); + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IMeasurement.cs b/src/OpenCensus.Abstractions/Stats/IMeasurement.cs new file mode 100644 index 000000000..2835e30f8 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IMeasurement.cs @@ -0,0 +1,42 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Measurements; + + /// + /// Represents a single measurement for a measure. + /// + public interface IMeasurement + { + /// + /// Gets the measure this measurement recorded for. + /// + IMeasure Measure { get; } + + /// + /// Calls the measure-type specific callback without type casting of a measurement. + /// + /// Result type of the callback. + /// Callback to be called for double measure. + /// Callback to be called for long measure. + /// Callback to be called for all other measures. + /// The result of measure type specific callback execution. + T Match(Func p0, Func p1, Func defaultFunction); + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IStatsComponent.cs b/src/OpenCensus.Abstractions/Stats/IStatsComponent.cs new file mode 100644 index 000000000..b75ff4123 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IStatsComponent.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + /// + /// Stats recording configuration. + /// + public interface IStatsComponent + { + /// + /// Gets the view manager that holds current set of aggregators. + /// + IViewManager ViewManager { get; } + + /// + /// Gets the stats recorder to record values to. + /// + IStatsRecorder StatsRecorder { get; } + + /// + /// Gets the state of the state collection. + /// + StatsCollectionState State { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IStatsRecorder.cs b/src/OpenCensus.Abstractions/Stats/IStatsRecorder.cs new file mode 100644 index 000000000..3f168558f --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IStatsRecorder.cs @@ -0,0 +1,30 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + /// + /// Stats recorder. Used to supply and record new measurements. + /// + public interface IStatsRecorder + { + /// + /// Returns the measure map to record values to. + /// + /// Measure maps to record values to. + IMeasureMap NewMeasureMap(); + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IView.cs b/src/OpenCensus.Abstractions/Stats/IView.cs new file mode 100644 index 000000000..f5c73103b --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IView.cs @@ -0,0 +1,52 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System.Collections.Generic; + using OpenCensus.Tags; + + /// + /// Stats recording view. + /// + public interface IView + { + /// + /// Gets the name of the view. + /// + IViewName Name { get; } + + /// + /// Gets the description of the view. + /// + string Description { get; } + + /// + /// Gets the measure this view record values for. + /// + IMeasure Measure { get; } + + /// + /// Gets the aggregation is used by this view. + /// + IAggregation Aggregation { get; } + + /// + /// Gets the columns (dimensions) recorded by this view. + /// + IReadOnlyList Columns { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IViewData.cs b/src/OpenCensus.Abstractions/Stats/IViewData.cs new file mode 100644 index 000000000..b172a002c --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IViewData.cs @@ -0,0 +1,48 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using OpenCensus.Tags; + + /// + /// Result data of the view aggregation. + /// + public interface IViewData + { + /// + /// Gets the view this data calculated for. + /// + IView View { get; } + + /// + /// Gets the aggregation data grouped by combination of tag values associated with this view data. + /// + IDictionary AggregationMap { get; } + + /// + /// Gets the timestamp of a start of this aggregation. + /// + DateTimeOffset Start { get; } + + /// + /// Gets the timestamp of the end of this aggregation. + /// + DateTimeOffset End { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IViewManager.cs b/src/OpenCensus.Abstractions/Stats/IViewManager.cs new file mode 100644 index 000000000..bceaa0402 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IViewManager.cs @@ -0,0 +1,44 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System.Collections.Generic; + + /// + /// View manager that holds all configured views. + /// + public interface IViewManager + { + /// + /// Gets all configured views. + /// + ISet AllExportedViews { get; } + + /// + /// Returns the view with specified view name. + /// + /// View name. + /// The view with the specified name. + IViewData GetView(IViewName view); + + /// + /// Registers a new view to be tracked. + /// + /// View to be registered. + void RegisterView(IView view); + } +} diff --git a/src/OpenCensus.Abstractions/Stats/IViewName.cs b/src/OpenCensus.Abstractions/Stats/IViewName.cs new file mode 100644 index 000000000..46af1ed56 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/IViewName.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + /// + /// View name. + /// + public interface IViewName + { + /// + /// Gets the string representation of the view name. + /// + string AsString { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementDouble.cs b/src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementDouble.cs new file mode 100644 index 000000000..3ae1f1ed8 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementDouble.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measurements +{ + /// + /// Represents a single measurment for aggregation as double. + /// + public interface IMeasurementDouble : IMeasurement + { + /// + /// Gets the long value for aggregation as double. + /// + double Value { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementLong.cs b/src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementLong.cs new file mode 100644 index 000000000..dcd8c69bc --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Measurements/IMeasurementLong.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measurements +{ + /// + /// Represents a single measurement for aggregation as a long. + /// + public interface IMeasurementLong : IMeasurement + { + /// + /// Gets the long value to be aggregated. + /// + long Value { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Measures/IMeasureDouble.cs b/src/OpenCensus.Abstractions/Stats/Measures/IMeasureDouble.cs new file mode 100644 index 000000000..8d6d40d4c --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Measures/IMeasureDouble.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measures +{ + /// + /// Represents a measure to use in aggregations for double values. + /// + public interface IMeasureDouble : IMeasure + { + } +} diff --git a/src/OpenCensus.Abstractions/Stats/Measures/IMeasureLong.cs b/src/OpenCensus.Abstractions/Stats/Measures/IMeasureLong.cs new file mode 100644 index 000000000..84a07ff4e --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/Measures/IMeasureLong.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measures +{ + /// + /// Represents a measure to use in aggregations for long values. + /// + public interface IMeasureLong : IMeasure + { + } +} diff --git a/src/OpenCensus.Abstractions/Stats/StatsCollectionState.cs b/src/OpenCensus.Abstractions/Stats/StatsCollectionState.cs new file mode 100644 index 000000000..854c1c257 --- /dev/null +++ b/src/OpenCensus.Abstractions/Stats/StatsCollectionState.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + /// + /// Stats collection state. + /// + public enum StatsCollectionState + { + /// + /// Collection is enabled. + /// + ENABLED, + + /// + /// Collection is disabled. + /// + DISABLED, + } +} diff --git a/src/OpenCensus.Abstractions/Tags/ITag.cs b/src/OpenCensus.Abstractions/Tags/ITag.cs new file mode 100644 index 000000000..cad592fcd --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/ITag.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + /// + /// Tag with the key and value. + /// + public interface ITag + { + /// + /// Gets the tag key. + /// + ITagKey Key { get; } + + /// + /// Gets the tag value. + /// + ITagValue Value { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Tags/ITagContext.cs b/src/OpenCensus.Abstractions/Tags/ITagContext.cs new file mode 100644 index 000000000..0683ec35a --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/ITagContext.cs @@ -0,0 +1,27 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System.Collections.Generic; + + /// + /// Collection of tags representing the tags context. + /// + public interface ITagContext : IEnumerable + { + } +} diff --git a/src/OpenCensus.Abstractions/Tags/ITagContextBuilder.cs b/src/OpenCensus.Abstractions/Tags/ITagContextBuilder.cs new file mode 100644 index 000000000..be68d32b3 --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/ITagContextBuilder.cs @@ -0,0 +1,53 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Common; + + /// + /// Tags context builder. + /// + public interface ITagContextBuilder + { + /// + /// Puts a new tag into context. + /// + /// Key of the tag to add. + /// Value of the tag to add. + /// Tag context builder for operations chaining. + ITagContextBuilder Put(ITagKey key, ITagValue value); + + /// + /// Removes tag with the given key from the context. + /// + /// Key of the tag to remove. + /// Tag context builder for operations chaining. + ITagContextBuilder Remove(ITagKey key); + + /// + /// Builds the tags context. + /// + /// Resulting tag context. + ITagContext Build(); + + /// + /// Builds tag context and save it as current. + /// + /// Scope control object. Dispose it to close a scope. + IScope BuildScoped(); + } +} diff --git a/src/OpenCensus.Abstractions/Tags/ITagKey.cs b/src/OpenCensus.Abstractions/Tags/ITagKey.cs new file mode 100644 index 000000000..c9a806857 --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/ITagKey.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + /// + /// Tag key. + /// + public interface ITagKey + { + /// + /// Gets the name of the key. + /// + string Name { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Tags/ITagValue.cs b/src/OpenCensus.Abstractions/Tags/ITagValue.cs new file mode 100644 index 000000000..3e21469ab --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/ITagValue.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + /// + /// Tag value. + /// + public interface ITagValue + { + /// + /// Gets tag value as string. + /// + string AsString { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Tags/ITagger.cs b/src/OpenCensus.Abstractions/Tags/ITagger.cs new file mode 100644 index 000000000..033650565 --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/ITagger.cs @@ -0,0 +1,62 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Common; + + /// + /// Tags API configuraiton. + /// + public interface ITagger + { + /// + /// Gets the empty tags context. + /// + ITagContext Empty { get; } + + /// + /// Gets the current tags context. + /// + ITagContext CurrentTagContext { get; } + + /// + /// Gets the empty tags builder. + /// + ITagContextBuilder EmptyBuilder { get; } + + /// + /// Gets the builder out of current context. + /// + ITagContextBuilder CurrentBuilder { get; } + + /// + /// Enters the scope of code where the given tag context is in the current context and + /// returns an object that represents that scope.The scope is exited when the returned object is + /// closed. + /// + /// Tags to set as current. + /// Scope object. Dispose to dissassociate tags context from the current context. + IScope WithTagContext(ITagContext tags); + + /// + /// Gets the builder from the tags context. + /// + /// Tags to pre-initialize builder with. + /// Tags context builder preinitialized with the given tags. + ITagContextBuilder ToBuilder(ITagContext tags); + } +} diff --git a/src/OpenCensus.Abstractions/Tags/ITagsComponent.cs b/src/OpenCensus.Abstractions/Tags/ITagsComponent.cs new file mode 100644 index 000000000..b55346b42 --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/ITagsComponent.cs @@ -0,0 +1,41 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Tags.Propagation; + + /// + /// Tagger configuration. + /// + public interface ITagsComponent + { + /// + /// Gets the tagger to operate with tags. + /// + ITagger Tagger { get; } + + /// + /// Gets the propagation component to use to serialize and deserialize tags on the wire. + /// + ITagPropagationComponent TagPropagationComponent { get; } + + /// + /// Gets the state of tagging API - enabled or disabled. + /// + TaggingState State { get; } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Abstractions/Tags/Propagation/ITagContextBinarySerializer.cs b/src/OpenCensus.Abstractions/Tags/Propagation/ITagContextBinarySerializer.cs new file mode 100644 index 000000000..ddc15727e --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/Propagation/ITagContextBinarySerializer.cs @@ -0,0 +1,38 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + /// + /// Binary serializer and deserializer of tags. + /// + public interface ITagContextBinarySerializer + { + /// + /// Converts tags into byte array. + /// + /// Tags to serialize. + /// Binary representation of tags. + byte[] ToByteArray(ITagContext tags); + + /// + /// Deserialize tags from byte array. + /// + /// Bytes to deserialize. + /// Tags deserialized from bytes. + ITagContext FromByteArray(byte[] bytes); + } +} diff --git a/src/OpenCensus.Abstractions/Tags/Propagation/ITagPropagationComponent.cs b/src/OpenCensus.Abstractions/Tags/Propagation/ITagPropagationComponent.cs new file mode 100644 index 000000000..4fcc0f180 --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/Propagation/ITagPropagationComponent.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + /// + /// Stores configuration of various propagation serializers for tags. + /// + public interface ITagPropagationComponent + { + /// + /// Gets binary serializer/deserializer for tags. + /// + ITagContextBinarySerializer BinarySerializer { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Tags/TagValues.cs b/src/OpenCensus.Abstractions/Tags/TagValues.cs new file mode 100644 index 000000000..9d3618ae4 --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/TagValues.cs @@ -0,0 +1,110 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System.Collections.Generic; + using OpenCensus.Abstractions.Utils; + + /// + /// Collection of tags. + /// + public sealed class TagValues + { + private TagValues(IReadOnlyList values) + { + this.Values = values; + } + + /// + /// Gets the collection of tag values. + /// + public IReadOnlyList Values { get; } + + /// + /// Create tag values out of list of tags. + /// + /// Values to create tag values from. + /// Resulting tag values collection. + public static TagValues Create(IReadOnlyList values) + { + return new TagValues(values); + } + + /// + public override string ToString() + { + return "TagValues{" + + "values=" + Collections.ToString(this.Values) + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is TagValues that) + { + if (this.Values.Count != that.Values.Count) + { + return false; + } + + for (int i = 0; i < this.Values.Count; i++) + { + if (this.Values[i] == null) + { + if (that.Values[i] != null) + { + return false; + } + } + else + { + if (!this.Values[i].Equals(that.Values[i])) + { + return false; + } + } + } + + return true; + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + foreach (var v in this.Values) + { + if (v != null) + { + h ^= v.GetHashCode(); + } + } + + return h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Tags/TaggingState.cs b/src/OpenCensus.Abstractions/Tags/TaggingState.cs new file mode 100644 index 000000000..1f359b8b7 --- /dev/null +++ b/src/OpenCensus.Abstractions/Tags/TaggingState.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + /// + /// State of tagging API. + /// + public enum TaggingState + { + /// + /// Tagging enabled. + /// + ENABLED, + + /// + /// Tagging disabled. + /// + DISABLED, + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Annotation.cs b/src/OpenCensus.Abstractions/Trace/Annotation.cs new file mode 100644 index 000000000..fd3ade908 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Annotation.cs @@ -0,0 +1,93 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using OpenCensus.Abstractions.Utils; + + public sealed class Annotation : IAnnotation + { + private static readonly ReadOnlyDictionary EmptyAttributes = + new ReadOnlyDictionary(new Dictionary()); + + internal Annotation(string description, IDictionary attributes) + { + this.Description = description ?? throw new ArgumentNullException("Null description"); + this.Attributes = attributes ?? throw new ArgumentNullException("Null attributes"); + } + + public string Description { get; } + + public IDictionary Attributes { get; } + + public static IAnnotation FromDescription(string description) + { + return new Annotation(description, EmptyAttributes); + } + + public static IAnnotation FromDescriptionAndAttributes(string description, IDictionary attributes) + { + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + IDictionary readOnly = new ReadOnlyDictionary(attributes); + return new Annotation(description, readOnly); + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (obj is Annotation annotation) + { + return this.Description.Equals(annotation.Description) && + this.Attributes.SequenceEqual(annotation.Attributes); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Description.GetHashCode(); + h *= 1000003; + h ^= this.Attributes.GetHashCode(); + return h; + } + + /// + public override string ToString() + { + return "Annotation{" + + "description=" + this.Description + ", " + + "attributes=" + Collections.ToString(this.Attributes) + + "}"; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/AttributeValue.cs b/src/OpenCensus.Abstractions/Trace/AttributeValue.cs new file mode 100644 index 000000000..48eb5c777 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/AttributeValue.cs @@ -0,0 +1,83 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// Attribute value. + /// + public abstract class AttributeValue : IAttributeValue + { + internal AttributeValue() + { + } + + /// + /// Creates string attribute value from value provided. + /// + /// String value. + /// Attribute value encapsulating the provided string value. + public static IAttributeValue StringAttributeValue(string stringValue) + { + if (stringValue == null) + { + throw new ArgumentNullException(nameof(stringValue)); + } + + return new AttributeValue(stringValue); + } + + /// + /// Creates long attribute value from value provided. + /// + /// Long value. + /// Attribute value encapsulating the provided long value. + public static IAttributeValue LongAttributeValue(long longValue) + { + return new AttributeValue(longValue); + } + + /// + /// Creates boolean attribute value from value provided. + /// + /// Boolean value. + /// Attribute value encapsulating the provided boolean value. + public static IAttributeValue BooleanAttributeValue(bool booleanValue) + { + return new AttributeValue(booleanValue); + } + + /// + /// Creates double attribute value from value provided. + /// + /// Double value. + /// Attribute value encapsulating the provided double value. + public static IAttributeValue DoubleAttributeValue(double doubleValue) + { + return new AttributeValue(doubleValue); + } + + /// + public abstract T Match( + Func stringFunction, + Func booleanFunction, + Func longFunction, + Func doubleFunction, + Func defaultFunction); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/CanonicalCode.cs b/src/OpenCensus.Abstractions/Trace/CanonicalCode.cs new file mode 100644 index 000000000..d51334003 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/CanonicalCode.cs @@ -0,0 +1,145 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Canonical result code of span execution. + /// + public enum CanonicalCode + { + /// + /// The operation completed successfully. + /// + Ok = 0, + + /// + /// The operation was cancelled (typically by the caller). + /// + Cancelled = 1, + + /// + /// Unknown error. An example of where this error may be returned is if a Status value received + /// from another address space belongs to an error-space that is not known in this address space. + /// Also errors raised by APIs that do not return enough error information may be converted to + /// this error. + /// + Unknown = 2, + + /// + /// Client specified an invalid argument. Note that this differs from FAILED_PRECONDITION. + /// INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the + /// system (e.g., a malformed file name). + /// + InvalidArgument = 3, + + /// + /// Deadline expired before operation could complete. For operations that change the state of the + /// system, this error may be returned even if the operation has completed successfully. For + /// example, a successful response from a server could have been delayed long enough for the + /// deadline to expire. + /// + DeadlineExceeded = 4, + + /// + /// Some requested entity (e.g., file or directory) was not found. + /// + NotFound = 5, + + /// + /// Some entity that we attempted to create (e.g., file or directory) already exists. + /// + AlreadyExists = 6, + + /// + /// The caller does not have permission to execute the specified operation. PERMISSION_DENIED + /// must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED + /// instead for those errors). PERMISSION_DENIED must not be used if the caller cannot be + /// identified (use UNAUTHENTICATED instead for those errors). + /// + PermissionDenied = 7, + + /// + /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system + /// is out of space. + /// + ResourceExhausted = 8, + + /// + /// Operation was rejected because the system is not in a state required for the operation's + /// execution. For example, directory to be deleted may be non-empty, an rmdir operation is + /// applied to a non-directory, etc. + /// A litmus test that may help a service implementor in deciding between FAILED_PRECONDITION, + /// ABORTED, and UNAVAILABLE: (a) Use UNAVAILABLE if the client can retry just the failing call. + /// (b) Use ABORTED if the client should retry at a higher-level (e.g., restarting a + /// read-modify-write sequence). (c) Use FAILED_PRECONDITION if the client should not retry until + /// the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory + /// is non-empty, FAILED_PRECONDITION should be returned since the client should not retry unless + /// they have first fixed up the directory by deleting files from it. + /// + FailedPrecondition = 9, + + /// + /// The operation was aborted, typically due to a concurrency issue like sequencer check + /// failures, transaction aborts, etc. + /// + Aborted = 10, + + /// + /// Operation was attempted past the valid range. E.g., seeking or reading past end of file. + /// + /// Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system + /// state changes. For example, a 32-bit file system will generate INVALID_ARGUMENT if asked to + /// read at an offset that is not in the range [0,2^32-1], but it will generate OUT_OF_RANGE if + /// asked to read from an offset past the current file size. + /// + /// There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE. We recommend + /// using OUT_OF_RANGE (the more specific error) when it applies so that callers who are + /// iterating through a space can easily look for an OUT_OF_RANGE error to detect when they are + /// done. + /// + OutOfRange = 11, + + /// + /// Operation is not implemented or not supported/enabled in this service. + /// + Unimplemented = 12, + + /// + /// Internal errors. Means some invariants expected by underlying system has been broken. If you + /// see one of these errors, something is very broken. + /// + Internal = 13, + + /// + /// The service is currently unavailable. This is a most likely a transient condition and may be + /// corrected by retrying with a backoff. + /// + /// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE. + /// + Unavailable = 14, + + /// + /// Unrecoverable data loss or corruption. + /// + DataLoss = 15, + + /// + /// The request does not have valid authentication credentials for the operation. + /// + Unauthenticated = 16, + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Config/ITraceConfig.cs b/src/OpenCensus.Abstractions/Trace/Config/ITraceConfig.cs new file mode 100644 index 000000000..66ad62076 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Config/ITraceConfig.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config +{ + /// + /// Trace configuration. + /// + public interface ITraceConfig + { + /// + /// Gets ths active trace parameters that can be updated in runtime. + /// + ITraceParams ActiveTraceParams { get; } + + /// + /// Updates the active trace parameters. + /// + /// New trace parameters to use. + void UpdateActiveTraceParams(ITraceParams traceParams); + } +} \ No newline at end of file diff --git a/src/OpenCensus.Abstractions/Trace/Config/ITraceParams.cs b/src/OpenCensus.Abstractions/Trace/Config/ITraceParams.cs new file mode 100644 index 000000000..a08d9e5d7 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Config/ITraceParams.cs @@ -0,0 +1,55 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config +{ + /// + /// Trace parameters that can be updates in runtime. + /// + public interface ITraceParams + { + /// + /// Gets the sampler. + /// + ISampler Sampler { get; } + + /// + /// Gets the maximum number of attributes on span. + /// + int MaxNumberOfAttributes { get; } + + /// + /// Gets that maximum Number of annotations on span. + /// + int MaxNumberOfAnnotations { get; } + + /// + /// Gets the maximum number of messages on span. + /// + int MaxNumberOfMessageEvents { get; } + + /// + /// Gets the maximum number of links on span. + /// + int MaxNumberOfLinks { get; } + + /// + /// Creates params builder preinitialized with the trace parameters supplied. + /// + /// Trace parameters builder. + TraceParamsBuilder ToBuilder(); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Config/TraceParams.cs b/src/OpenCensus.Abstractions/Trace/Config/TraceParams.cs new file mode 100644 index 000000000..2332ff132 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Config/TraceParams.cs @@ -0,0 +1,136 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config +{ + using System; + using OpenCensus.Trace.Sampler; + + /// + public sealed class TraceParams : ITraceParams + { + /// + /// Default trace parameters. + /// + public static readonly ITraceParams Default = + new TraceParams(Samplers.GetProbabilitySampler(DefaultProbability), DefaultSpanMaxNumAttributes, DefaultSpanMaxNumAnnotations, DefaultSpanMaxNumMessageEvents, DefaultSpanMaxNumLinks); + + private const double DefaultProbability = 1e-4; + private const int DefaultSpanMaxNumAttributes = 32; + private const int DefaultSpanMaxNumAnnotations = 32; + private const int DefaultSpanMaxNumMessageEvents = 128; + private const int DefaultSpanMaxNumLinks = 128; + + internal TraceParams(ISampler sampler, int maxNumberOfAttributes, int maxNumberOfAnnotations, int maxNumberOfMessageEvents, int maxNumberOfLinks) + { + if (maxNumberOfAttributes <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxNumberOfAttributes)); + } + + if (maxNumberOfAnnotations <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxNumberOfAnnotations)); + } + + if (maxNumberOfMessageEvents <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxNumberOfMessageEvents)); + } + + if (maxNumberOfLinks <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxNumberOfLinks)); + } + + this.Sampler = sampler ?? throw new ArgumentNullException(nameof(sampler)); + this.MaxNumberOfAttributes = maxNumberOfAttributes; + this.MaxNumberOfAnnotations = maxNumberOfAnnotations; + this.MaxNumberOfMessageEvents = maxNumberOfMessageEvents; + this.MaxNumberOfLinks = maxNumberOfLinks; + } + + /// + public ISampler Sampler { get; } + + /// + public int MaxNumberOfAttributes { get; } + + /// + public int MaxNumberOfAnnotations { get; } + + /// + public int MaxNumberOfMessageEvents { get; } + + /// + public int MaxNumberOfLinks { get; } + + /// + public TraceParamsBuilder ToBuilder() + { + return new TraceParamsBuilder(this); + } + + /// + public override string ToString() + { + return "TraceParams{" + + "sampler=" + this.Sampler + ", " + + "maxNumberOfAttributes=" + this.MaxNumberOfAttributes + ", " + + "maxNumberOfAnnotations=" + this.MaxNumberOfAnnotations + ", " + + "maxNumberOfMessageEvents=" + this.MaxNumberOfMessageEvents + ", " + + "maxNumberOfLinks=" + this.MaxNumberOfLinks + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is TraceParams that) + { + return this.Sampler.Equals(that.Sampler) + && (this.MaxNumberOfAttributes == that.MaxNumberOfAttributes) + && (this.MaxNumberOfAnnotations == that.MaxNumberOfAnnotations) + && (this.MaxNumberOfMessageEvents == that.MaxNumberOfMessageEvents) + && (this.MaxNumberOfLinks == that.MaxNumberOfLinks); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Sampler.GetHashCode(); + h *= 1000003; + h ^= this.MaxNumberOfAttributes; + h *= 1000003; + h ^= this.MaxNumberOfAnnotations; + h *= 1000003; + h ^= this.MaxNumberOfMessageEvents; + h *= 1000003; + h ^= this.MaxNumberOfLinks; + return h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Config/TraceParamsBuilder.cs b/src/OpenCensus.Abstractions/Trace/Config/TraceParamsBuilder.cs new file mode 100644 index 000000000..b42c5641a --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Config/TraceParamsBuilder.cs @@ -0,0 +1,141 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config +{ + using System; + + /// + /// Trace parameters builder. + /// + public sealed class TraceParamsBuilder + { + private ISampler sampler; + private int? maxNumberOfAttributes; + private int? maxNumberOfAnnotations; + private int? maxNumberOfMessageEvents; + private int? maxNumberOfLinks; + + internal TraceParamsBuilder(TraceParams source) + { + this.sampler = source.Sampler; + this.maxNumberOfAttributes = source.MaxNumberOfAttributes; + this.maxNumberOfAnnotations = source.MaxNumberOfAnnotations; + this.maxNumberOfMessageEvents = source.MaxNumberOfMessageEvents; + this.maxNumberOfLinks = source.MaxNumberOfLinks; + } + + /// + /// Sets sempler to use. + /// + /// Sampler to use. + /// Builder to chain operations. + public TraceParamsBuilder SetSampler(ISampler sampler) + { + this.sampler = sampler ?? throw new ArgumentNullException("Null sampler"); + return this; + } + + /// + /// Sets the maximum number of attributes. + /// + /// Maximum number of attributes per span. + /// Builder to chain operations. + public TraceParamsBuilder SetMaxNumberOfAttributes(int maxNumberOfAttributes) + { + this.maxNumberOfAttributes = maxNumberOfAttributes; + return this; + } + + /// + /// Sets the maximum number of annotations. + /// + /// Maximum number of annotations per span. + /// Builder to chain operations. + public TraceParamsBuilder SetMaxNumberOfAnnotations(int maxNumberOfAnnotations) + { + this.maxNumberOfAnnotations = maxNumberOfAnnotations; + return this; + } + + /// + /// Sets the maximum number of message events on span. + /// + /// Maximum number of message events per span. + /// Builder to chain operations. + public TraceParamsBuilder SetMaxNumberOfMessageEvents(int maxNumberOfMessageEvents) + { + this.maxNumberOfMessageEvents = maxNumberOfMessageEvents; + return this; + } + + /// + /// Sets the maximum number of links on span. + /// + /// Maximum number of links on span. + /// Builder to chain operations. + public TraceParamsBuilder SetMaxNumberOfLinks(int maxNumberOfLinks) + { + this.maxNumberOfLinks = maxNumberOfLinks; + return this; + } + + /// + /// Builds trace parameters from provided arguments. + /// + /// Builder to chain operations. + public TraceParams Build() + { + string missing = string.Empty; + if (this.sampler == null) + { + missing += " sampler"; + } + + if (!this.maxNumberOfAttributes.HasValue) + { + missing += " maxNumberOfAttributes"; + } + + if (!this.maxNumberOfAnnotations.HasValue) + { + missing += " maxNumberOfAnnotations"; + } + + if (!this.maxNumberOfMessageEvents.HasValue) + { + missing += " maxNumberOfMessageEvents"; + } + + if (!this.maxNumberOfLinks.HasValue) + { + missing += " maxNumberOfLinks"; + } + + if (!string.IsNullOrEmpty(missing)) + { + throw new ArgumentOutOfRangeException("Missing required properties:" + missing); + } + + return new TraceParams( + this.sampler, + this.maxNumberOfAttributes.Value, + this.maxNumberOfAnnotations.Value, + this.maxNumberOfMessageEvents.Value, + this.maxNumberOfLinks.Value); + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/EndSpanOptions.cs b/src/OpenCensus.Abstractions/Trace/EndSpanOptions.cs new file mode 100644 index 000000000..db5f7d36b --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/EndSpanOptions.cs @@ -0,0 +1,95 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// End span options. + /// + public class EndSpanOptions + { + /// + /// Default span completion options. + /// + public static readonly EndSpanOptions Default = new EndSpanOptions(false); + + internal EndSpanOptions() + { + } + + internal EndSpanOptions(bool sampleToLocalSpanStore, Status status = null) + { + this.SampleToLocalSpanStore = sampleToLocalSpanStore; + this.Status = status; + } + + /// + /// Gets a value indicating whether span needs to be stored into local store. + /// + public bool SampleToLocalSpanStore { get; } + + /// + /// Gets the span status. + /// + public Status Status { get; } + + /// + /// Gets the span builder. + /// + /// Returns builder to build span options. + public static EndSpanOptionsBuilder Builder() + { + return new EndSpanOptionsBuilder().SetSampleToLocalSpanStore(false); + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (obj is EndSpanOptions that) + { + return (this.SampleToLocalSpanStore == that.SampleToLocalSpanStore) + && ((this.Status == null) ? (that.Status == null) : this.Status.Equals(that.Status)); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.SampleToLocalSpanStore ? 1231 : 1237; + h *= 1000003; + h ^= (this.Status == null) ? 0 : this.Status.GetHashCode(); + return h; + } + + /// + public override string ToString() + { + return "EndSpanOptions{" + + "sampleToLocalSpanStore=" + this.SampleToLocalSpanStore + ", " + + "status=" + this.Status + + "}"; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/EndSpanOptionsBuilder.cs b/src/OpenCensus.Abstractions/Trace/EndSpanOptionsBuilder.cs new file mode 100644 index 000000000..89933011c --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/EndSpanOptionsBuilder.cs @@ -0,0 +1,77 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// End span options builder. + /// + public class EndSpanOptionsBuilder + { + private bool? sampleToLocalSpanStore; + private Status status; + + internal EndSpanOptionsBuilder() + { + } + + /// + /// Indicate whether span is intended for local spans store. + /// + /// Value indicating whether span is intended for local span store. + /// Span options builder for operations chaining. + public EndSpanOptionsBuilder SetSampleToLocalSpanStore(bool sampleToLocalSpanStore) + { + this.sampleToLocalSpanStore = sampleToLocalSpanStore; + return this; + } + + /// + /// Sets the span status. + /// + /// Span status. + /// Span options builder for the operations chaining. + public EndSpanOptionsBuilder SetStatus(Status status) + { + this.status = status; + return this; + } + + /// + /// Builds the span options. + /// + /// Span options instance. + public EndSpanOptions Build() + { + string missing = string.Empty; + if (!this.sampleToLocalSpanStore.HasValue) + { + missing += " sampleToLocalSpanStore"; + } + + if (!string.IsNullOrEmpty(missing)) + { + throw new ArgumentOutOfRangeException("Missing required properties:" + missing); + } + + return new EndSpanOptions( + this.sampleToLocalSpanStore.Value, + this.status); + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/Attributes.cs b/src/OpenCensus.Abstractions/Trace/Export/Attributes.cs new file mode 100644 index 000000000..3f6583ab1 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/Attributes.cs @@ -0,0 +1,84 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + public sealed class Attributes : IAttributes + { + internal Attributes(IDictionary attributeMap, int droppedAttributesCount) + { + this.AttributeMap = attributeMap ?? throw new ArgumentNullException("Null attributeMap"); + this.DroppedAttributesCount = droppedAttributesCount; + } + + public IDictionary AttributeMap { get; } + + public int DroppedAttributesCount { get; } + + public static Attributes Create(IDictionary attributeMap, int droppedAttributesCount) + { + if (attributeMap == null) + { + throw new ArgumentNullException(nameof(attributeMap)); + } + + IDictionary copy = new Dictionary(attributeMap); + return new Attributes(new ReadOnlyDictionary(copy), droppedAttributesCount); + } + + /// + public override string ToString() + { + return "Attributes{" + + "attributeMap=" + this.AttributeMap + ", " + + "droppedAttributesCount=" + this.DroppedAttributesCount + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Attributes that) + { + return this.AttributeMap.SequenceEqual(that.AttributeMap) + && (this.DroppedAttributesCount == that.DroppedAttributesCount); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.AttributeMap.GetHashCode(); + h *= 1000003; + h ^= this.DroppedAttributesCount; + return h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/IAttributes.cs b/src/OpenCensus.Abstractions/Trace/Export/IAttributes.cs new file mode 100644 index 000000000..b76fdb530 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/IAttributes.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Attributes collection. + /// + public interface IAttributes + { + /// + /// Gets tha dictionaty of attributes by name. + /// + IDictionary AttributeMap { get; } + + /// + /// Gets the number of attributed dropped due to the limit. + /// + int DroppedAttributesCount { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/IExportComponent.cs b/src/OpenCensus.Abstractions/Trace/Export/IExportComponent.cs new file mode 100644 index 000000000..bcb462daf --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/IExportComponent.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + /// + /// Component that exports spans. + /// + public interface IExportComponent + { + /// + /// Gets the span exoprter. + /// + ISpanExporter SpanExporter { get; } + + /// + /// Gets the running span store. + /// + IRunningSpanStore RunningSpanStore { get; } + + /// + /// Gets the sampled span store. + /// + ISampledSpanStore SampledSpanStore { get; } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Abstractions/Trace/Export/IHandler.cs b/src/OpenCensus.Abstractions/Trace/Export/IHandler.cs new file mode 100644 index 000000000..6970a144e --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/IHandler.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// Exporter handler. + /// + public interface IHandler + { + /// + /// Exports the list of spans to the backend. + /// + /// Collection of spans to export. + /// A representing the asynchronous export operation. + Task ExportAsync(IEnumerable spanDataList); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ILinks.cs b/src/OpenCensus.Abstractions/Trace/Export/ILinks.cs new file mode 100644 index 000000000..bdafd9108 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ILinks.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Collection of links. + /// + public interface ILinks + { + /// + /// Gets the list of links. + /// + IEnumerable Links { get; } + + /// + /// Gets the number of dropped links due to exceeding the maximum links count limit. + /// + int DroppedLinksCount { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/IRunningPerSpanNameSummary.cs b/src/OpenCensus.Abstractions/Trace/Export/IRunningPerSpanNameSummary.cs new file mode 100644 index 000000000..544f8e935 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/IRunningPerSpanNameSummary.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + /// + /// Running spans summary. + /// + public interface IRunningPerSpanNameSummary + { + /// + /// Gets the number of the running span. + /// + int NumRunningSpans { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStore.cs b/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStore.cs new file mode 100644 index 000000000..4ca3823d0 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStore.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Running spans store. + /// + public interface IRunningSpanStore + { + /// + /// Gets the summary of this store. + /// + IRunningSpanStoreSummary Summary { get; } + + /// + /// Gets the list of all running spans with the applied filter. + /// + /// Filter to apply to query running spans. + /// List of currently running spans. + IEnumerable GetRunningSpans(IRunningSpanStoreFilter filter); + + /// + /// Called when span got started. + /// + /// Span that was just started. + void OnStart(ISpan span); + + /// + /// Called when span just ended. + /// + /// Span that just ended. + void OnEnd(ISpan span); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreFilter.cs b/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreFilter.cs new file mode 100644 index 000000000..f787cc368 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreFilter.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + /// + /// Filter to query spans from the running spans store. + /// + public interface IRunningSpanStoreFilter + { + /// + /// Gets the name of the span to query. + /// + string SpanName { get; } + + /// + /// Gets the maximum number of spans to return. + /// + int MaxSpansToReturn { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreSummary.cs b/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreSummary.cs new file mode 100644 index 000000000..b1d666ae0 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/IRunningSpanStoreSummary.cs @@ -0,0 +1,31 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Summary of all running spans. + /// + public interface IRunningSpanStoreSummary + { + /// + /// Gets the summary of running spans per span name. + /// + IDictionary PerSpanNameSummary { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISampledLatencyBucketBoundaries.cs b/src/OpenCensus.Abstractions/Trace/Export/ISampledLatencyBucketBoundaries.cs new file mode 100644 index 000000000..ef4e8ade3 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISampledLatencyBucketBoundaries.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + + /// + /// Sampoled spans latency buckets for histograms calculations. + /// + public interface ISampledLatencyBucketBoundaries + { + /// + /// Gets the lower latency boundary. + /// + TimeSpan LatencyLower { get; } + + /// + /// Gets the upper latency boundary. + /// + TimeSpan LatencyUpper { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISampledPerSpanNameSummary.cs b/src/OpenCensus.Abstractions/Trace/Export/ISampledPerSpanNameSummary.cs new file mode 100644 index 000000000..9dbbd8de0 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISampledPerSpanNameSummary.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Summary of sampled spans. + /// + public interface ISampledPerSpanNameSummary + { + /// + /// Gets the number of sampled spans by latency boundary. + /// + IDictionary NumbersOfLatencySampledSpans { get; } + + /// + /// Gets the number of error sampled spans by error code. + /// + IDictionary NumbersOfErrorSampledSpans { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStore.cs b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStore.cs new file mode 100644 index 000000000..80f9ddaf3 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStore.cs @@ -0,0 +1,68 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Samples spans store. + /// + public interface ISampledSpanStore + { + /// + /// Gets the summary of sampled spans. + /// + ISampledSpanStoreSummary Summary { get; } + + /// + /// Gets all registered span names. + /// + ISet RegisteredSpanNamesForCollection { get; } + + /// + /// Gets the list of sampled spans using the provided filter. + /// + /// Filter to use to query sampled store. + /// List of spans satisfying filtering criteria. + IEnumerable GetLatencySampledSpans(ISampledSpanStoreLatencyFilter filter); + + /// + /// Gets the list of error spans using provided error filter. + /// + /// Filter to use to query store. + /// List of sampled spans satisfying filtering criteria. + IEnumerable GetErrorSampledSpans(ISampledSpanStoreErrorFilter filter); + + /// + /// Registers span names for collection. + /// + /// List of span names. + void RegisterSpanNamesForCollection(IEnumerable spanNames); + + /// + /// Unregister span names for the collection. + /// + /// Span names to unregister. + void UnregisterSpanNamesForCollection(IEnumerable spanNames); + + /// + /// Consider span for sampling. + /// + /// Span to consider. + void ConsiderForSampling(ISpan span); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreErrorFilter.cs b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreErrorFilter.cs new file mode 100644 index 000000000..b8fc1dab9 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreErrorFilter.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + /// + /// Sampled spans store error filter. + /// + public interface ISampledSpanStoreErrorFilter + { + /// + /// Gets the span name to use to filter. + /// + string SpanName { get; } + + /// + /// Gets the cannonical code to use to filter. + /// + CanonicalCode? CanonicalCode { get; } + + /// + /// Gets the maximum number of spans to return. + /// + int MaxSpansToReturn { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreLatencyFilter.cs b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreLatencyFilter.cs new file mode 100644 index 000000000..e97ba355d --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreLatencyFilter.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + + /// + /// Sampled span store latency filter. + /// + public interface ISampledSpanStoreLatencyFilter + { + /// + /// Gets the span name to filter by. + /// + string SpanName { get; } + + /// + /// Gets the latency lower boundery to filter by. + /// + TimeSpan LatencyLower { get; } + + /// + /// Gets the latency upper boundary to filter by. + /// + TimeSpan LatencyUpper { get; } + + /// + /// Gets the maximum number of spans to return. + /// + int MaxSpansToReturn { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreSummary.cs b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreSummary.cs new file mode 100644 index 000000000..980b50af2 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISampledSpanStoreSummary.cs @@ -0,0 +1,31 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Summary of sampled spans store. + /// + public interface ISampledSpanStoreSummary + { + /// + /// Gets the collection of summaries by span name. + /// + IDictionary PerSpanNameSummary { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISpanData.cs b/src/OpenCensus.Abstractions/Trace/Export/ISpanData.cs new file mode 100644 index 000000000..0fef166af --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISpanData.cs @@ -0,0 +1,91 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using OpenCensus.Common; + + /// + /// Span data with read-only properties. + /// + public interface ISpanData + { + /// + /// Gets the span context. + /// + ISpanContext Context { get; } + + /// + /// Gets the parent span id. + /// + ISpanId ParentSpanId { get; } + + /// + /// Gets a value indicating whether span has a remote parent. + /// + bool? HasRemoteParent { get; } + + /// + /// Gets the span name. + /// + string Name { get; } + + /// + /// Gets the start timestamp. + /// + Timestamp StartTimestamp { get; } + + /// + /// Gets the collection of attributes. + /// + IAttributes Attributes { get; } + + /// + /// Gets the collection of annotations. + /// + ITimedEvents Annotations { get; } + + /// + /// Gets the collection of message events. + /// + ITimedEvents MessageEvents { get; } + + /// + /// Gets the links collection. + /// + ILinks Links { get; } + + /// + /// Gets the childer span count. + /// + int? ChildSpanCount { get; } + + /// + /// Gets the span result status. + /// + Status Status { get; } + + /// + /// Gets the span kind. + /// + SpanKind Kind { get; } + + /// + /// Gets the end timestamp. + /// + Timestamp EndTimestamp { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ISpanExporter.cs b/src/OpenCensus.Abstractions/Trace/Export/ISpanExporter.cs new file mode 100644 index 000000000..d8c506358 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ISpanExporter.cs @@ -0,0 +1,58 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Span exporter. + /// + public interface ISpanExporter : IDisposable + { + /// + /// Adds a single span to the exporter. + /// + /// Span to export. + void AddSpan(ISpan span); + + /// + /// Exports collection of spans. This method is used for the situation when the + /// span objects have been created from external sources, not using the Open Censis API. + /// For example, read from file or generated from objects recieved in async queue. + /// + /// Set of objects to export. + /// Cancellation token. + /// A representing asynchronous export operation. + Task ExportAsync(IEnumerable export, CancellationToken token); + + /// + /// Registers the exporter handler. + /// + /// Name of the handler. + /// Handler instance. + void RegisterHandler(string name, IHandler handler); + + /// + /// Unregister handler by it's name. + /// + /// Name of the handler to unregister. + void UnregisterHandler(string name); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ITimedEvent.cs b/src/OpenCensus.Abstractions/Trace/Export/ITimedEvent.cs new file mode 100644 index 000000000..4e9382ba5 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ITimedEvent.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using OpenCensus.Common; + + /// + /// Timed event. + /// + /// Type of the timed event. + public interface ITimedEvent + { + /// + /// Gets the timestamp associated with this timed event. + /// + Timestamp Timestamp { get; } + + /// + /// Gets the typed event content. + /// + T Event { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/ITimedEvents.cs b/src/OpenCensus.Abstractions/Trace/Export/ITimedEvents.cs new file mode 100644 index 000000000..838f3bb72 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/ITimedEvents.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + /// + /// Collection of timed events associated with the span. + /// + /// Type of an event. + public interface ITimedEvents + { + /// + /// Gets ths list of timed events. + /// + IEnumerable> Events { get; } + + /// + /// Gets the number of dropped events due to active limits. + /// + int DroppedEventsCount { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/LinkList.cs b/src/OpenCensus.Abstractions/Trace/Export/LinkList.cs new file mode 100644 index 000000000..f699c7007 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/LinkList.cs @@ -0,0 +1,84 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public sealed class LinkList : ILinks + { + internal LinkList(IEnumerable links, int droppedLinksCount) + { + this.Links = links ?? throw new ArgumentNullException("Null links"); + this.DroppedLinksCount = droppedLinksCount; + } + + public int DroppedLinksCount { get; } + + public IEnumerable Links { get; } + + public static LinkList Create(IEnumerable links, int droppedLinksCount) + { + if (links == null) + { + throw new ArgumentNullException(nameof(links)); + } + + IEnumerable copy = new List(links); + + return new LinkList(copy, droppedLinksCount); + } + + /// + public override string ToString() + { + return "Links{" + + "links=" + this.Links + ", " + + "droppedLinksCount=" + this.DroppedLinksCount + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is LinkList that) + { + return this.Links.SequenceEqual(that.Links) + && (this.DroppedLinksCount == that.DroppedLinksCount); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Links.GetHashCode(); + h *= 1000003; + h ^= this.DroppedLinksCount; + return h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/SpanData.cs b/src/OpenCensus.Abstractions/Trace/Export/SpanData.cs new file mode 100644 index 000000000..625d087ec --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/SpanData.cs @@ -0,0 +1,204 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Common; + + public sealed class SpanData : ISpanData + { + internal SpanData( + ISpanContext context, + ISpanId parentSpanId, + bool? hasRemoteParent, + string name, + Timestamp startTimestamp, + IAttributes attributes, + ITimedEvents annotations, + ITimedEvents messageEvents, + ILinks links, + int? childSpanCount, + Status status, + SpanKind kind, + Timestamp endTimestamp) + { + this.Context = context ?? throw new ArgumentNullException(nameof(context)); + this.ParentSpanId = parentSpanId; + this.HasRemoteParent = hasRemoteParent; + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.StartTimestamp = startTimestamp ?? throw new ArgumentNullException(nameof(startTimestamp)); + this.Attributes = attributes ?? Export.Attributes.Create(new Dictionary(), 0); + this.Annotations = annotations ?? TimedEvents.Create(Enumerable.Empty>(), 0); + this.MessageEvents = messageEvents ?? TimedEvents.Create(Enumerable.Empty>(), 0); + this.Links = links ?? LinkList.Create(Enumerable.Empty(), 0); + this.ChildSpanCount = childSpanCount; + this.Status = status; + this.Kind = kind; + this.EndTimestamp = endTimestamp; + } + + public ISpanContext Context { get; } + + public ISpanId ParentSpanId { get; } + + public bool? HasRemoteParent { get; } + + public string Name { get; } + + public Timestamp Timestamp { get; } + + public IAttributes Attributes { get; } + + public ITimedEvents Annotations { get; } + + public ITimedEvents MessageEvents { get; } + + public ILinks Links { get; } + + public int? ChildSpanCount { get; } + + public Status Status { get; } + + public SpanKind Kind { get; } + + public Timestamp EndTimestamp { get; } + + public Timestamp StartTimestamp { get; } + + public static ISpanData Create( + ISpanContext context, + ISpanId parentSpanId, + bool? hasRemoteParent, + string name, + Timestamp startTimestamp, + IAttributes attributes, + ITimedEvents annotations, + ITimedEvents messageOrNetworkEvents, + ILinks links, + int? childSpanCount, + Status status, + SpanKind kind, + Timestamp endTimestamp) + { + if (messageOrNetworkEvents == null) + { + messageOrNetworkEvents = TimedEvents.Create(new List>(), 0); + } + + var messageEventsList = new List>(); + foreach (ITimedEvent timedEvent in messageOrNetworkEvents.Events) + { + messageEventsList.Add(timedEvent); + } + + ITimedEvents messageEvents = TimedEvents.Create(messageEventsList, messageOrNetworkEvents.DroppedEventsCount); + return new SpanData( + context, + parentSpanId, + hasRemoteParent, + name, + startTimestamp, + attributes, + annotations, + messageEvents, + links, + childSpanCount, + status, + kind, + endTimestamp); + } + + /// + public override string ToString() + { + return "SpanData{" + + "context=" + this.Context + ", " + + "parentSpanId=" + this.ParentSpanId + ", " + + "hasRemoteParent=" + this.HasRemoteParent + ", " + + "name=" + this.Name + ", " + + "startTimestamp=" + this.StartTimestamp + ", " + + "attributes=" + this.Attributes + ", " + + "annotations=" + this.Annotations + ", " + + "messageEvents=" + this.MessageEvents + ", " + + "links=" + this.Links + ", " + + "childSpanCount=" + this.ChildSpanCount + ", " + + "status=" + this.Status + ", " + + "endTimestamp=" + this.EndTimestamp + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is SpanData that) + { + return this.Context.Equals(that.Context) + && ((this.ParentSpanId == null) ? (that.ParentSpanId == null) : this.ParentSpanId.Equals(that.ParentSpanId)) + && this.HasRemoteParent.Equals(that.HasRemoteParent) + && this.Name.Equals(that.Name) + && this.StartTimestamp.Equals(that.StartTimestamp) + && this.Attributes.Equals(that.Attributes) + && this.Annotations.Equals(that.Annotations) + && this.MessageEvents.Equals(that.MessageEvents) + && this.Links.Equals(that.Links) + && ((this.ChildSpanCount == null) ? (that.ChildSpanCount == null) : this.ChildSpanCount.Equals(that.ChildSpanCount)) + && ((this.Status == null) ? (that.Status == null) : this.Status.Equals(that.Status)) + && ((this.EndTimestamp == null) ? (that.EndTimestamp == null) : this.EndTimestamp.Equals(that.EndTimestamp)); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Context.GetHashCode(); + h *= 1000003; + h ^= (this.ParentSpanId == null) ? 0 : this.ParentSpanId.GetHashCode(); + h *= 1000003; + h ^= (this.HasRemoteParent == null) ? 0 : this.HasRemoteParent.GetHashCode(); + h *= 1000003; + h ^= this.Name.GetHashCode(); + h *= 1000003; + h ^= this.StartTimestamp.GetHashCode(); + h *= 1000003; + h ^= this.Attributes.GetHashCode(); + h *= 1000003; + h ^= this.Annotations.GetHashCode(); + h *= 1000003; + h ^= this.MessageEvents.GetHashCode(); + h *= 1000003; + h ^= this.Links.GetHashCode(); + h *= 1000003; + h ^= (this.ChildSpanCount == null) ? 0 : this.ChildSpanCount.GetHashCode(); + h *= 1000003; + h ^= (this.Status == null) ? 0 : this.Status.GetHashCode(); + h *= 1000003; + h ^= (this.EndTimestamp == null) ? 0 : this.EndTimestamp.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/TimedEvent.cs b/src/OpenCensus.Abstractions/Trace/Export/TimedEvent.cs new file mode 100644 index 000000000..4db63f9fb --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/TimedEvent.cs @@ -0,0 +1,75 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using OpenCensus.Common; + + public sealed class TimedEvent : ITimedEvent + { + internal TimedEvent(Timestamp timestamp, T @event) + { + this.Timestamp = timestamp; + this.Event = @event; + } + + public Timestamp Timestamp { get; } + + public T Event { get; } + + public static ITimedEvent Create(Timestamp timestamp, T @event) + { + return new TimedEvent(timestamp, @event); + } + + /// + public override string ToString() + { + return "TimedEvent{" + + "timestamp=" + this.Timestamp + ", " + + "event=" + this.Event + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is TimedEvent that) + { + return this.Timestamp.Equals(that.Timestamp) + && this.Event.Equals(that.Event); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Timestamp.GetHashCode(); + h *= 1000003; + h ^= this.Event.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Export/TimedEvents.cs b/src/OpenCensus.Abstractions/Trace/Export/TimedEvents.cs new file mode 100644 index 000000000..c709863e6 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Export/TimedEvents.cs @@ -0,0 +1,82 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public sealed class TimedEvents : ITimedEvents + { + internal TimedEvents(IEnumerable> events, int droppedEventsCount) + { + this.Events = events ?? throw new ArgumentNullException("Null events"); + this.DroppedEventsCount = droppedEventsCount; + } + + public IEnumerable> Events { get; } + + public int DroppedEventsCount { get; } + + public static ITimedEvents Create(IEnumerable> events, int droppedEventsCount) + { + if (events == null) + { + throw new ArgumentNullException(nameof(events)); + } + + return new TimedEvents(events, droppedEventsCount); + } + + /// + public override string ToString() + { + return "TimedEvents{" + + "events=" + this.Events + ", " + + "droppedEventsCount=" + this.DroppedEventsCount + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is TimedEvents that) + { + return this.Events.SequenceEqual(that.Events) + && (this.DroppedEventsCount == that.DroppedEventsCount); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Events.GetHashCode(); + h *= 1000003; + h ^= this.DroppedEventsCount; + return h; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/IAnnotation.cs b/src/OpenCensus.Abstractions/Trace/IAnnotation.cs new file mode 100644 index 000000000..43ba6c12b --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/IAnnotation.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System.Collections.Generic; + + /// + /// Annotation associated with the span. + /// + public interface IAnnotation + { + /// + /// Gets the annotation description. + /// + string Description { get; } + + /// + /// Gets the collection of attributes associated with the annotation. + /// + IDictionary Attributes { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/IAttributeValue.cs b/src/OpenCensus.Abstractions/Trace/IAttributeValue.cs new file mode 100644 index 000000000..b672d44b3 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/IAttributeValue.cs @@ -0,0 +1,63 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// Attribute value. + /// + public interface IAttributeValue + { + /// + /// Executes type-specific callback without type casting. + /// + /// Callback return value. + /// Callback to call for string. + /// Callback to call for boolean. + /// Callback to call for long. + /// Callback to call for double. + /// Callback to call for any other type. + /// Callback execution result. + T Match( + Func stringFunction, + Func booleanFunction, + Func longFunction, + Func doubleFunction, + Func defaultFunction); + } + + /// + /// Generic attribute value interface. + /// + /// Type of the attribute. + public interface IAttributeValue : IAttributeValue + { + /// + /// Gets the attribute value. + /// + TAttr Value { get; } + + /// + /// Executes type specific callback. + /// + /// Callback result type. + /// Callback to execute. + /// Result of callback execution. + T Apply(Func function); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ILink.cs b/src/OpenCensus.Abstractions/Trace/ILink.cs new file mode 100644 index 000000000..e74114d7b --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ILink.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System.Collections.Generic; + + /// + /// Link associated with the span. + /// + public interface ILink + { + /// + /// Gets the trace ID of the linked span. + /// + ITraceId TraceId { get; } + + /// + /// Gets the span ID of the linked span. + /// + ISpanId SpanId { get; } + + /// + /// Gets the type of the link. + /// + LinkType Type { get; } + + /// + /// Gets the collection of attributes associated with the link. + /// + IDictionary Attributes { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/IMessageEvent.cs b/src/OpenCensus.Abstractions/Trace/IMessageEvent.cs new file mode 100644 index 000000000..f6b73164b --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/IMessageEvent.cs @@ -0,0 +1,44 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Message event happened during the span execution. + /// + public interface IMessageEvent + { + /// + /// Gets ths type of the event. + /// + MessageEventType Type { get; } + + /// + /// Gets the message identitifer associated with the event. + /// + long MessageId { get; } + + /// + /// Gets the uncompressed message size. + /// + long UncompressedMessageSize { get; } + + /// + /// Gets the compressed message size. + /// + long CompressedMessageSize { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/IRandomGenerator.cs b/src/OpenCensus.Abstractions/Trace/IRandomGenerator.cs new file mode 100644 index 000000000..0594eaf3f --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/IRandomGenerator.cs @@ -0,0 +1,23 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + public interface IRandomGenerator + { + void NextBytes(byte[] bytes); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ISampler.cs b/src/OpenCensus.Abstractions/Trace/ISampler.cs new file mode 100644 index 000000000..721119f6e --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ISampler.cs @@ -0,0 +1,47 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System.Collections.Generic; + + /// + /// Sampler to reduce data volume. This sampler executes before Span object was created. + /// + public interface ISampler + { + /// + /// Gets the span description. + /// + string Description { get; } + + /// + /// Checks whether span needs to be created and tracked. + /// + /// Parent span context. Typically taken from the wire. + /// Indicates whether it was a remote parent. + /// Trace ID of a span to be created. + /// Span ID of a span to be created. + /// Name of a span to be created. Note, that the name of the span is settable. + /// So this name can be changed later and implementation should assume that. + /// Typical example of a name change is when representing incoming http request + /// has a name of url path and then being updated with route name when rouing complete. + /// + /// Links associated with the parent span. + /// True of span needs to be created. False otherwise. + bool ShouldSample(ISpanContext parentContext, bool hasRemoteParent, ITraceId traceId, ISpanId spanId, string name, IEnumerable parentLinks); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ISpan.cs b/src/OpenCensus.Abstractions/Trace/ISpan.cs new file mode 100644 index 000000000..25075f3aa --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ISpan.cs @@ -0,0 +1,115 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System.Collections.Generic; + + /// + /// Span represents the execution of the certain span of code or span of time between two events which is part of + /// a distributed trace and has result of execution, context of executuion and other properties. + /// + /// This class is mostly write only. Span should not be used to exchange information. Only to add properties + /// to it for monitoring purposes. It will be converted to SpanData that is readable. + /// + public interface ISpan + { + /// + /// Gets or sets the span name. + /// + string Name { get; set; } + + /// + /// Gets the span context. + /// + ISpanContext Context { get; } + + /// + /// Gets the span options. + /// + SpanOptions Options { get; } + + /// + /// Gets or sets the status of the span execution. + /// + Status Status { get; set; } + + /// + /// Gets or sets the kind of a span. + /// + SpanKind? Kind { get; set; } + + /// + /// Gets a value indicating whether this span was already stopped. + /// + bool HasEnded { get; } + + /// + /// Puts a new attribute to the span. + /// + /// Key of the attribute. + /// Attribute value. + void PutAttribute(string key, IAttributeValue value); + + /// + /// Puts a list of attributes to the span. + /// + /// Collection of attributes name/value pairs. + void PutAttributes(IDictionary attributes); + + /// + /// Adds a single annotation to the span. + /// + /// Annotation description. + void AddAnnotation(string description); + + /// + /// Adds a single annotation with the attributes to the span. + /// + /// Annotation description. + /// Collection of attributes name/value pairs associted with the annotation. + void AddAnnotation(string description, IDictionary attributes); + + /// + /// Adds an annotation to the span. + /// + /// Annotation to add to the span. + void AddAnnotation(IAnnotation annotation); + + /// + /// Adds the message even to the span. + /// + /// Message event to add to the span. + void AddMessageEvent(IMessageEvent messageEvent); + + /// + /// Adds link to the span. + /// + /// Link to add to the span. + void AddLink(ILink link); + + /// + /// Complete the span and set end span options. + /// + /// Span completion options. + void End(EndSpanOptions options); + + /// + /// End the span. + /// + void End(); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ISpanBuilder.cs b/src/OpenCensus.Abstractions/Trace/ISpanBuilder.cs new file mode 100644 index 000000000..8ecae92de --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ISpanBuilder.cs @@ -0,0 +1,70 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using OpenCensus.Common; + + /// + /// Span builder. + /// + public interface ISpanBuilder + { + /// + /// Set the sampler for the span. + /// + /// Sampler to use to build span. + /// This span builder for chaining. + ISpanBuilder SetSampler(ISampler sampler); + + /// + /// Set the parent links on the span. + /// + /// Parent links to set on span. + /// This span builder for chaining. + ISpanBuilder SetParentLinks(IEnumerable parentLinks); + + /// + /// Set the record events value. + /// + /// Value indicating whether to record span. + /// This span builder for chaining. + ISpanBuilder SetRecordEvents(bool recordEvents); + + /// + /// Starts the span. + /// + /// Span that was just started. + ISpan StartSpan(); + + /// + /// Starts the span and set it as a current on the current context. + /// + /// Scoped event to control the scope of span in the context. + /// Dispose to stop the span and disassiciate it from the current context. + IScope StartScopedSpan(); + + /// + /// Starts the span and set it as a current on the current context, setting the out param to current span. + /// + /// Current span. + /// Scoped event to control the scope of span in the context. + /// Dispose to stop the span and disassiciate it from the current context. + IScope StartScopedSpan(out ISpan currentSpan); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ISpanContext.cs b/src/OpenCensus.Abstractions/Trace/ISpanContext.cs new file mode 100644 index 000000000..c32aafe73 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ISpanContext.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Context associated with the span. + /// + public interface ISpanContext + { + /// + /// Gets tracestate collection that allows different vendors to participate in a trace. + /// + Tracestate Tracestate { get; } + + /// + /// Gets trace identifier. + /// + ITraceId TraceId { get; } + + /// + /// Gets stan identifier. + /// + ISpanId SpanId { get; } + + /// + /// Gets trace options. + /// + TraceOptions TraceOptions { get; } + + /// + /// Gets a value indicating whether the span is valid. + /// + bool IsValid { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ISpanId.cs b/src/OpenCensus.Abstractions/Trace/ISpanId.cs new file mode 100644 index 000000000..fa2bced76 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ISpanId.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// Span identifier. + /// + public interface ISpanId : IComparable + { + /// + /// Gets the span identifier as bytes. + /// + byte[] Bytes { get; } + + /// + /// Gets a value indicating whether span identifier is valid. + /// + bool IsValid { get; } + + /// + /// Copy span id as bytes into destination byte array. + /// + /// Destination byte array. + /// Offset to start writing from. + void CopyBytesTo(byte[] dest, int destOffset); + + /// + /// Gets the span identifier as a string. + /// + /// String representation of Span identifier. + string ToLowerBase16(); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/IStartEndHandler.cs b/src/OpenCensus.Abstractions/Trace/IStartEndHandler.cs new file mode 100644 index 000000000..2a1a01542 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/IStartEndHandler.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Start event handler. + /// + public interface IStartEndHandler + { + /// + /// Called when span is being started. + /// + /// Span that just started. + void OnStart(ISpan span); + + /// + /// Called when span is just ended. + /// + /// Span that was just ended. + void OnEnd(ISpan span); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ITraceComponent.cs b/src/OpenCensus.Abstractions/Trace/ITraceComponent.cs new file mode 100644 index 000000000..901fe2631 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ITraceComponent.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Common; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Propagation; + + /// + /// Trace component holds all the extensibility points required for distributed tracing. + /// + public interface ITraceComponent + { + /// + /// Gets the tracer to record Spans. + /// + ITracer Tracer { get; } + + /// + /// Gets the propagation component that defines how to extract and inject the context from the wire protocols. + /// + IPropagationComponent PropagationComponent { get; } + + /// + /// Gets the exporter to use to upload spans. + /// + IExportComponent ExportComponent { get; } + + /// + /// Gets the tracer configuration. Include sampling definition and limits. + /// + ITraceConfig TraceConfig { get; } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ITraceId.cs b/src/OpenCensus.Abstractions/Trace/ITraceId.cs new file mode 100644 index 000000000..2000e5335 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ITraceId.cs @@ -0,0 +1,54 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// Trace ID. + /// + public interface ITraceId : IComparable + { + /// + /// Gets the bytes representation of a trace id. + /// + byte[] Bytes { get; } + + /// + /// Gets a value indicating whether trace if is valid. + /// + bool IsValid { get; } + + /// + /// Gets the lower long of the trace ID. + /// + long LowerLong { get; } + + /// + /// Copy trace ID as bytes into the destination bytes array at a given offset. + /// + /// Destination bytes array. + /// Desitnation bytes array offset. + void CopyBytesTo(byte[] dest, int destOffset); + + /// + /// Gets the lower base 16 representaiton of the trace id. + /// + /// Canonical string representation of a trace id. + string ToLowerBase16(); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/ITracer.cs b/src/OpenCensus.Abstractions/Trace/ITracer.cs new file mode 100644 index 000000000..0ef2ed7c5 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/ITracer.cs @@ -0,0 +1,64 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Common; + + /// + /// Tracer to record distributed tracing informaiton. + /// + public interface ITracer + { + /// + /// Gets the current span from the context. + /// + ISpan CurrentSpan { get; } + + /// + /// Associates the span with the current context. + /// + /// Span to associate with the current context. + /// Scope object to control span to current context association. + IScope WithSpan(ISpan span); + + /// + /// Gets the span builder for the span with the given name. + /// + /// Span name. + /// Span kind. + /// Span builder for the span with the given name. + ISpanBuilder SpanBuilder(string spanName, SpanKind spanKind = SpanKind.Unspecified); + + /// + /// Gets the span builder for the span with the given name and parent. + /// + /// Span name. + /// Span kind. + /// Parent of the span. + /// Span builder for the span with the given name and specified parent. + ISpanBuilder SpanBuilderWithExplicitParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpan parent = null); + + /// + /// Gets the span builder for the span with the give name and remote parent context. + /// + /// Span name. + /// Span kind. + /// Remote parent context extracted from the wire. + /// Span builder for the span with the given name and specified parent span context. + ISpanBuilder SpanBuilderWithRemoteParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpanContext remoteParentSpanContext = null); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Internal/AttributeValue{T}.cs b/src/OpenCensus.Abstractions/Trace/Internal/AttributeValue{T}.cs new file mode 100644 index 000000000..181a34a8e --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Internal/AttributeValue{T}.cs @@ -0,0 +1,151 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// Generic attribute value. + /// + /// Type of the value carried by this attribute value. + internal sealed class AttributeValue : AttributeValue, IAttributeValue + { + internal AttributeValue(T value) + { + this.Value = value; + } + + /// + public T Value { get; } + + /// + /// Creates an attribute value from string. + /// + /// Value to populate attribute value with. + /// Attribute value with the given value. + public static IAttributeValue Create(string stringValue) + { + if (stringValue == null) + { + throw new ArgumentNullException(nameof(stringValue)); + } + + return new AttributeValue(stringValue); + } + + /// + /// Creates an attribute value from long. + /// + /// Value to populate attribute value with. + /// Attribute value with the given value. + public static IAttributeValue Create(long longValue) + { + return new AttributeValue(longValue); + } + + /// + /// Creates an attribute value from bool. + /// + /// Value to populate attribute value with. + /// Attribute value with the given value. + public static IAttributeValue Create(bool booleanValue) + { + return new AttributeValue(booleanValue); + } + + /// + /// Creates an attribute value from double. + /// + /// Value to populate attribute value with. + /// Attribute value with the given value. + public static IAttributeValue Create(double doubleValue) + { + return new AttributeValue(doubleValue); + } + + /// + public TArg Apply(Func function) + { + return function(this.Value); + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (obj is AttributeValue attribute) + { + return attribute.Value.Equals(this.Value); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Value.GetHashCode(); + return h; + } + + /// + public override string ToString() + { + return "AttributeValue{" + + "Value=" + this.Value.ToString() + + "}"; + } + + /// + public override TReturn Match( + Func stringFunction, + Func booleanFunction, + Func longFunction, + Func doubleFunction, + Func defaultFunction) + { + if (typeof(T) == typeof(string)) + { + string value = this.Value as string; + return stringFunction(value); + } + else if (typeof(T) == typeof(long)) + { + long val = (long)(object)this.Value; + return longFunction(val); + } + else if (typeof(T) == typeof(bool)) + { + bool val = (bool)(object)this.Value; + return booleanFunction(val); + } + else if (typeof(T) == typeof(double)) + { + double val = (double)(object)this.Value; + return doubleFunction(val); + } + + return defaultFunction(this.Value); + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/LinkType.cs b/src/OpenCensus.Abstractions/Trace/LinkType.cs new file mode 100644 index 000000000..efee27e8e --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/LinkType.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Link type. + /// + public enum LinkType + { + /// + /// The relationship of the two spans is unknown, or known but other than parent-child. + /// + Unspecified = 0, + + /// + /// The linked span is a child of the current span. + /// + ChildLinkedSpan = 1, + + /// + /// The linked span is a parent of the current span. + /// + ParentLinkedSpan = 2, + } +} diff --git a/src/OpenCensus.Abstractions/Trace/MessageEventType.cs b/src/OpenCensus.Abstractions/Trace/MessageEventType.cs new file mode 100644 index 000000000..6057580ec --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/MessageEventType.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Messages event type. + /// + public enum MessageEventType + { + /// + /// Unknown event type. + /// + Unspecified = 0, + + /// + /// Represent message that was sent from client. + /// + Sent = 1, + + /// + /// Represents message that was recieved by the client. + /// + Received = 2, + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Propagation/IBinaryFormat.cs b/src/OpenCensus.Abstractions/Trace/Propagation/IBinaryFormat.cs new file mode 100644 index 000000000..2361f01af --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Propagation/IBinaryFormat.cs @@ -0,0 +1,38 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + /// + /// Binary format propagator. + /// + public interface IBinaryFormat + { + /// + /// Deserializes span context from the bytes array. + /// + /// Bytes array with the envoded span context in it. + /// Span context deserialized from the byte array. + ISpanContext FromByteArray(byte[] bytes); + + /// + /// Serialize span context into the bytes array. + /// + /// Span context to serialize. + /// Byte array with the encoded span context in it. + byte[] ToByteArray(ISpanContext spanContext); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Propagation/IPropagationComponent.cs b/src/OpenCensus.Abstractions/Trace/Propagation/IPropagationComponent.cs new file mode 100644 index 000000000..49d0546d1 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Propagation/IPropagationComponent.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + /// + /// Configuration of wire protocol to use to extract and inject span context from the wire. + /// + public interface IPropagationComponent + { + /// + /// Gets the binary format propagator. + /// + IBinaryFormat BinaryFormat { get; } + + /// + /// Gets the text format propagator. + /// + ITextFormat TextFormat { get; } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Abstractions/Trace/Propagation/ITextFormat.cs b/src/OpenCensus.Abstractions/Trace/Propagation/ITextFormat.cs new file mode 100644 index 000000000..d454bd4f7 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Propagation/ITextFormat.cs @@ -0,0 +1,53 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using System; + using System.Collections.Generic; + + /// + /// Text format wire context propagator. Helps to extract and inject context from textual + /// representation (typically http headers or metadata colleciton). + /// + public interface ITextFormat + { + /// + /// Gets the list of headers used by propagator. The use cases of this are: + /// * allow pre-allocation of fields, especially in systems like gRPC Metadata + /// * allow a single-pass over an iterator (ex OpenTracing has no getter in TextMap). + /// + ISet Fields { get; } + + /// + /// Injects textual representation of span context to transmit over the wire. + /// + /// Type of an object to set context on. Typically HttpRequest or similar. + /// Span context to transmit over the wire. + /// Object to set context on. Instance of this object will be passed to setter. + /// Action that will set name and value pair on the object. + void Inject(ISpanContext spanContext, T carrier, Action setter); + + /// + /// Extracts span context from textual representation. + /// + /// Type of object to extract context from. Typically HttpRequest or similar. + /// Object to extract context from. Instance of this object will be passed to the getter. + /// Function that will return string value of a key with the specified name. + /// Span context from it's text representation. + ISpanContext Extract(T carrier, Func> getter); + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Sampler/AlwaysSampleSampler.cs b/src/OpenCensus.Abstractions/Trace/Sampler/AlwaysSampleSampler.cs new file mode 100644 index 000000000..f9abd4c18 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Sampler/AlwaysSampleSampler.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Sampler +{ + using System.Collections.Generic; + + internal class AlwaysSampleSampler : ISampler + { + internal AlwaysSampleSampler() + { + } + + public string Description + { + get + { + return this.ToString(); + } + } + + public bool ShouldSample(ISpanContext parentContext, bool hasRemoteParent, ITraceId traceId, ISpanId spanId, string name, IEnumerable parentLinks) + { + return true; + } + + /// + public override string ToString() + { + return "AlwaysSampleSampler"; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Sampler/NeverSampleSampler.cs b/src/OpenCensus.Abstractions/Trace/Sampler/NeverSampleSampler.cs new file mode 100644 index 000000000..61e1ae1f1 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Sampler/NeverSampleSampler.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Sampler +{ + using System.Collections.Generic; + + internal sealed class NeverSampleSampler : ISampler + { + internal NeverSampleSampler() + { + } + + public string Description + { + get + { + return this.ToString(); + } + } + + public bool ShouldSample(ISpanContext parentContext, bool hasRemoteParent, ITraceId traceId, ISpanId spanId, string name, IEnumerable parentLinks) + { + return false; + } + + /// + public override string ToString() + { + return "NeverSampleSampler"; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Sampler/ProbabilitySampler.cs b/src/OpenCensus.Abstractions/Trace/Sampler/ProbabilitySampler.cs new file mode 100644 index 000000000..6b15939d9 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Sampler/ProbabilitySampler.cs @@ -0,0 +1,139 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Sampler +{ + using System; + using System.Collections.Generic; + using OpenCensus.Utils; + + internal sealed class ProbabilitySampler : ISampler + { + internal ProbabilitySampler(double probability, long idUpperBound) + { + this.Probability = probability; + this.IdUpperBound = idUpperBound; + } + + public string Description + { + get + { + return string.Format("ProbabilitySampler({0:F6})", this.Probability); + } + } + + public double Probability { get; } + + public long IdUpperBound { get; } + + public bool ShouldSample(ISpanContext parentContext, bool hasRemoteParent, ITraceId traceId, ISpanId spanId, string name, IEnumerable parentLinks) + { + // If the parent is sampled keep the sampling decision. + if (parentContext != null && parentContext.TraceOptions.IsSampled) + { + return true; + } + + if (parentLinks != null) + { + // If any parent link is sampled keep the sampling decision. + foreach (ISpan parentLink in parentLinks) + { + if (parentLink.Context.TraceOptions.IsSampled) + { + return true; + } + } + } + + // Always sample if we are within probability range. This is true even for child spans (that + // may have had a different sampling decision made) to allow for different sampling policies, + // and dynamic increases to sampling probabilities for debugging purposes. + // Note use of '<' for comparison. This ensures that we never sample for probability == 0.0, + // while allowing for a (very) small chance of *not* sampling if the id == Long.MAX_VALUE. + // This is considered a reasonable tradeoff for the simplicity/performance requirements (this + // code is executed in-line for every Span creation). + return Math.Abs(traceId.LowerLong) < this.IdUpperBound; + } + + /// + public override string ToString() + { + return "ProbabilitySampler{" + + "probability=" + this.Probability + ", " + + "idUpperBound=" + this.IdUpperBound + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is ProbabilitySampler that) + { + return DoubleUtil.ToInt64(this.Probability) == DoubleUtil.ToInt64(that.Probability) + && (this.IdUpperBound == that.IdUpperBound); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Probability) >> 32) ^ DoubleUtil.ToInt64(this.Probability); + h *= 1000003; + h ^= (this.IdUpperBound >> 32) ^ this.IdUpperBound; + return (int)h; + } + + internal static ProbabilitySampler Create(double probability) + { + if (probability < 0.0 || probability > 1.0) + { + throw new ArgumentOutOfRangeException("probability must be in range [0.0, 1.0]"); + } + + long idUpperBound; + + // Special case the limits, to avoid any possible issues with lack of precision across + // double/long boundaries. For probability == 0.0, we use Long.MIN_VALUE as this guarantees + // that we will never sample a trace, even in the case where the id == Long.MIN_VALUE, since + // Math.Abs(Long.MIN_VALUE) == Long.MIN_VALUE. + if (probability == 0.0) + { + idUpperBound = long.MinValue; + } + else if (probability == 1.0) + { + idUpperBound = long.MaxValue; + } + else + { + idUpperBound = (long)(probability * long.MaxValue); + } + + return new ProbabilitySampler(probability, idUpperBound); + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Sampler/Samplers.cs b/src/OpenCensus.Abstractions/Trace/Sampler/Samplers.cs new file mode 100644 index 000000000..d28c2e7d3 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Sampler/Samplers.cs @@ -0,0 +1,59 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Sampler +{ + /// + /// Factory of well-known samplers. + /// + public sealed class Samplers + { + private static readonly ISampler AlwaysSampleInstance = new AlwaysSampleSampler(); + private static readonly ISampler NeverSampleInstance = new NeverSampleSampler(); + + /// + /// Gets the sampler that always sample. + /// + public static ISampler AlwaysSample + { + get + { + return AlwaysSampleInstance; + } + } + + /// + /// Gets the sampler than never samples. + /// + public static ISampler NeverSample + { + get + { + return NeverSampleInstance; + } + } + + /// + /// Gets the probability sampler. + /// + /// Probability to use. + /// Sampler that samples with the given probability. + public static ISampler GetProbabilitySampler(double probability) + { + return ProbabilitySampler.Create(probability); + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/SpanAttributeConstants.cs b/src/OpenCensus.Abstractions/Trace/SpanAttributeConstants.cs new file mode 100644 index 000000000..7a8f2445f --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/SpanAttributeConstants.cs @@ -0,0 +1,31 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + internal static class SpanAttributeConstants + { + public const string HttpMethodKey = "http.method"; + public const string HttpStatusCodeKey = "http.status_code"; + public const string HttpUserAgentKey = "http.user_agent"; + public const string HttpPathKey = "http.path"; + public const string HttpHostKey = "http.host"; + public const string HttpUrlKey = "http.url"; + public const string HttpRequestSizeKey = "http.request.size"; + public const string HttpResponseSizeKey = "http.response.size"; + public const string HttpRouteKey = "http.route"; + } +} diff --git a/src/OpenCensus.Abstractions/Trace/SpanContext.cs b/src/OpenCensus.Abstractions/Trace/SpanContext.cs new file mode 100644 index 000000000..dc93c40fa --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/SpanContext.cs @@ -0,0 +1,90 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// A class that represents a span context. A span context contains the state that must propagate to + /// child and across process boundaries. It contains the identifiers + /// and associated with the and a set of . + /// + public sealed class SpanContext : ISpanContext + { + public static readonly SpanContext Invalid = new SpanContext(Trace.TraceId.Invalid, Trace.SpanId.Invalid, TraceOptions.Default, Tracestate.Empty); + + private SpanContext(ITraceId traceId, ISpanId spanId, TraceOptions traceOptions, Tracestate tracestate) + { + this.TraceId = traceId; + this.SpanId = spanId; + this.TraceOptions = traceOptions; + this.Tracestate = tracestate; + } + + public ITraceId TraceId { get; } + + public ISpanId SpanId { get; } + + public TraceOptions TraceOptions { get; } + + public bool IsValid => this.TraceId.IsValid && this.SpanId.IsValid; + + public Tracestate Tracestate { get; } + + public static ISpanContext Create(ITraceId traceId, ISpanId spanId, TraceOptions traceOptions, Tracestate tracestate) + { + return new SpanContext(traceId, spanId, traceOptions, tracestate); + } + + /// + public override int GetHashCode() + { + int result = 1; + result = (31 * result) + (this.TraceId == null ? 0 : this.TraceId.GetHashCode()); + result = (31 * result) + (this.SpanId == null ? 0 : this.SpanId.GetHashCode()); + result = (31 * result) + (this.TraceOptions == null ? 0 : this.TraceOptions.GetHashCode()); + return result; + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj is SpanContext)) + { + return false; + } + + SpanContext that = (SpanContext)obj; + return this.TraceId.Equals(that.TraceId) + && this.SpanId.Equals(that.SpanId) + && this.TraceOptions.Equals(that.TraceOptions); + } + + /// + public override string ToString() + { + return "SpanContext{" + + "traceId=" + this.TraceId + ", " + + "spanId=" + this.SpanId + ", " + + "traceOptions=" + this.TraceOptions + + "}"; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/SpanExtensions.cs b/src/OpenCensus.Abstractions/Trace/SpanExtensions.cs new file mode 100644 index 000000000..70b48662e --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/SpanExtensions.cs @@ -0,0 +1,224 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Helper class to populate well-known span attributes. + /// + public static class SpanExtensions + { + /// + /// Helper method that populates span properties from http method according + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Http method. + /// Span with populated http method properties. + public static ISpan PutHttpMethodAttribute(this ISpan span, string method) + { + span.PutAttribute(SpanAttributeConstants.HttpMethodKey, AttributeValue.StringAttributeValue(method)); + return span; + } + + /// + /// Helper method that populates span properties from http status code according + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Http status code. + /// Span with populated status code properties. + public static ISpan PutHttpStatusCodeAttribute(this ISpan span, int statusCode) + { + span.PutAttribute(SpanAttributeConstants.HttpStatusCodeKey, AttributeValue.LongAttributeValue(statusCode)); + return span; + } + + /// + /// Helper method that populates span properties from http user agent according + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Http status code. + /// Span with populated user agent code properties. + public static ISpan PutHttpUserAgentAttribute(this ISpan span, string userAgent) + { + if (!string.IsNullOrWhiteSpace(userAgent)) + { + span.PutAttribute(SpanAttributeConstants.HttpUserAgentKey, AttributeValue.StringAttributeValue(userAgent)); + } + + return span; + } + + /// + /// Helper method that populates span properties from host and port + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Hostr name. + /// Port number. + /// Span with populated host properties. + public static ISpan PutHttpHostAttribute(this ISpan span, string hostName, int port) + { + if (port == 80 || port == 443) + { + span.PutAttribute(SpanAttributeConstants.HttpHostKey, AttributeValue.StringAttributeValue(hostName)); + } + else + { + span.PutAttribute(SpanAttributeConstants.HttpHostKey, AttributeValue.StringAttributeValue(hostName + ":" + port)); + } + + return span; + } + + /// + /// Helper method that populates span properties from route + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Route used to resolve url to controller. + /// Span with populated route properties. + public static ISpan PutHttpRouteAttribute(this ISpan span, string route) + { + if (!string.IsNullOrEmpty(route)) + { + span.PutAttribute(SpanAttributeConstants.HttpRouteKey, AttributeValue.StringAttributeValue(route)); + } + + return span; + } + + /// + /// Helper method that populates span properties from host and port + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Raw url. + /// Span with populated url properties. + public static ISpan PutHttpRawUrlAttribute(this ISpan span, string rawUrl) + { + if (!string.IsNullOrEmpty(rawUrl)) + { + span.PutAttribute(SpanAttributeConstants.HttpUrlKey, AttributeValue.StringAttributeValue(rawUrl)); + } + + return span; + } + + /// + /// Helper method that populates span properties from url path according + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Url path. + /// Span with populated path properties. + public static ISpan PutHttpPathAttribute(this ISpan span, string path) + { + span.PutAttribute(SpanAttributeConstants.HttpPathKey, AttributeValue.StringAttributeValue(path)); + return span; + } + + /// + /// Helper method that populates span properties from size according + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Response size. + /// Span with populated response size properties. + public static ISpan PutHttpResponseSizeAttribute(this ISpan span, long size) + { + span.PutAttribute(SpanAttributeConstants.HttpResponseSizeKey, AttributeValue.LongAttributeValue(size)); + return span; + } + + /// + /// Helper method that populates span properties from request size according + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Request size. + /// Span with populated request size properties. + public static ISpan PutHttpRequestSizeAttribute(this ISpan span, long size) + { + span.PutAttribute(SpanAttributeConstants.HttpRequestSizeKey, AttributeValue.LongAttributeValue(size)); + return span; + } + + /// + /// Helper method that populates span properties from http status code according + /// to https://github.com/census-instrumentation/opencensus-specs/blob/4954074adf815f437534457331178194f6847ff9/trace/HTTP.md. + /// + /// Span to fill out. + /// Http status code. + /// Http reason phrase. + /// Span with populated properties. + public static ISpan PutHttpStatusCode(this ISpan span, int statusCode, string reasonPhrase) + { + span.PutHttpStatusCodeAttribute(statusCode); + + if ((int)statusCode < 200) + { + span.Status = Status.Unknown; + } + else if ((int)statusCode >= 200 && (int)statusCode <= 399) + { + span.Status = Status.Ok; + } + else if ((int)statusCode == 400) + { + span.Status = Status.InvalidArgument; + } + else if ((int)statusCode == 401) + { + span.Status = Status.Unauthenticated; + } + else if ((int)statusCode == 403) + { + span.Status = Status.PermissionDenied; + } + else if ((int)statusCode == 404) + { + span.Status = Status.NotFound; + } + else if ((int)statusCode == 429) + { + span.Status = Status.ResourceExhausted; + } + else if ((int)statusCode == 501) + { + span.Status = Status.Unimplemented; + } + else if ((int)statusCode == 503) + { + span.Status = Status.Unavailable; + } + else if ((int)statusCode == 504) + { + span.Status = Status.DeadlineExceeded; + } + else + { + span.Status = Status.Unknown; + } + + span.Status = span.Status.WithDescription(reasonPhrase); + + return span; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/SpanId.cs b/src/OpenCensus.Abstractions/Trace/SpanId.cs new file mode 100644 index 000000000..02a79091b --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/SpanId.cs @@ -0,0 +1,163 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using OpenCensus.Utils; + + public sealed class SpanId : ISpanId + { + public const int Size = 8; + + private static readonly SpanId InvalidSpanId = new SpanId(new byte[Size]); + + private readonly byte[] bytes; + + private SpanId(byte[] bytes) + { + this.bytes = bytes; + } + + public static ISpanId Invalid + { + get + { + return InvalidSpanId; + } + } + + public byte[] Bytes + { + get + { + byte[] copyOf = new byte[Size]; + Buffer.BlockCopy(this.bytes, 0, copyOf, 0, Size); + return copyOf; + } + } + + public bool IsValid + { + get { return !Arrays.Equals(this.bytes, InvalidSpanId.bytes); } + } + + public static ISpanId FromBytes(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (buffer.Length != Size) + { + throw new ArgumentOutOfRangeException(string.Format("Invalid size: expected {0}, got {1}", Size, buffer.Length)); + } + + byte[] bytesCopied = new byte[Size]; + Buffer.BlockCopy(buffer, 0, bytesCopied, 0, Size); + return new SpanId(bytesCopied); + } + + public static ISpanId FromBytes(byte[] src, int srcOffset) + { + byte[] bytes = new byte[Size]; + Buffer.BlockCopy(src, srcOffset, bytes, 0, Size); + return new SpanId(bytes); + } + + public static ISpanId FromLowerBase16(string src) + { + if (src.Length != 2 * Size) + { + throw new ArgumentOutOfRangeException(string.Format("Invalid size: expected {0}, got {1}", 2 * Size, src.Length)); + } + + byte[] bytes = Arrays.StringToByteArray(src); + return new SpanId(bytes); + } + + public static ISpanId GenerateRandomId(IRandomGenerator random) + { + byte[] bytes = new byte[Size]; + do + { + random.NextBytes(bytes); + } + while (Arrays.Equals(bytes, InvalidSpanId.bytes)); + return new SpanId(bytes); + } + + public void CopyBytesTo(byte[] dest, int destOffset) + { + Buffer.BlockCopy(this.bytes, 0, dest, destOffset, Size); + } + + public string ToLowerBase16() + { + var bytes = this.Bytes; + return Arrays.ByteArrayToString(bytes); + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj is SpanId)) + { + return false; + } + + SpanId that = (SpanId)obj; + return Arrays.Equals(this.bytes, that.bytes); + } + + /// + public override int GetHashCode() + { + return Arrays.GetHashCode(this.bytes); + } + + /// + public override string ToString() + { + return "SpanId{" + + "bytes=" + this.ToLowerBase16() + + "}"; + } + + public int CompareTo(ISpanId other) + { + SpanId that = other as SpanId; + for (int i = 0; i < Size; i++) + { + if (this.bytes[i] != that.bytes[i]) + { + sbyte b1 = (sbyte)this.bytes[i]; + sbyte b2 = (sbyte)that.bytes[i]; + + return b1 < b2 ? -1 : 1; + } + } + + return 0; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/SpanKind.cs b/src/OpenCensus.Abstractions/Trace/SpanKind.cs new file mode 100644 index 000000000..375568337 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/SpanKind.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Span kind. + /// + public enum SpanKind + { + /// + /// Span kind was not specified. + /// + Unspecified = 0, + + /// + /// Server span represents request incoming from external component. + /// + Server = 1, + + /// + /// Client span represents outgoing request to the external component. + /// + Client = 2, + } +} \ No newline at end of file diff --git a/src/OpenCensus.Abstractions/Trace/SpanOptions.cs b/src/OpenCensus.Abstractions/Trace/SpanOptions.cs new file mode 100644 index 000000000..34b810bad --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/SpanOptions.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// Span recording options. + /// + [Flags] + public enum SpanOptions + { + /// + /// None + /// + None = 0x0, + + /// + /// Record events. + /// + RecordEvents = 0x1, + } +} diff --git a/src/OpenCensus.Abstractions/Trace/Status.cs b/src/OpenCensus.Abstractions/Trace/Status.cs new file mode 100644 index 000000000..6321c24ce --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/Status.cs @@ -0,0 +1,222 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Span execution status. + /// + public class Status + { + /// + /// The operation completed successfully. + /// + public static readonly Status Ok = new Status(CanonicalCode.Ok); + + /// + /// The operation was cancelled (typically by the caller). + /// + public static readonly Status Cancelled = new Status(CanonicalCode.Cancelled); + + /// + /// Unknown error. An example of where this error may be returned is if a Status value received + /// from another address space belongs to an error-space that is not known in this address space. + /// Also errors raised by APIs that do not return enough error information may be converted to + /// this error. + /// + public static readonly Status Unknown = new Status(CanonicalCode.Unknown); + + /// + /// Client specified an invalid argument. Note that this differs from FAILED_PRECONDITION. + /// INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the + /// system (e.g., a malformed file name). + /// + public static readonly Status InvalidArgument = new Status(CanonicalCode.InvalidArgument); + + /// + /// Deadline expired before operation could complete. For operations that change the state of the + /// system, this error may be returned even if the operation has completed successfully. For + /// example, a successful response from a server could have been delayed long enough for the + /// deadline to expire. + /// + public static readonly Status DeadlineExceeded = new Status(CanonicalCode.DeadlineExceeded); + + /// + /// Some requested entity (e.g., file or directory) was not found. + /// + public static readonly Status NotFound = new Status(CanonicalCode.NotFound); + + /// + /// Some entity that we attempted to create (e.g., file or directory) already exists. + /// + public static readonly Status AlreadyExists = new Status(CanonicalCode.AlreadyExists); + + /// + /// The caller does not have permission to execute the specified operation. PERMISSION_DENIED + /// must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED + /// instead for those errors). PERMISSION_DENIED must not be used if the caller cannot be + /// identified (use UNAUTHENTICATED instead for those errors). + /// + public static readonly Status PermissionDenied = new Status(CanonicalCode.PermissionDenied); + + /// + /// The request does not have valid authentication credentials for the operation. + /// + public static readonly Status Unauthenticated = new Status(CanonicalCode.Unauthenticated); + + /// + /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system + /// is out of space. + /// + public static readonly Status ResourceExhausted = new Status(CanonicalCode.ResourceExhausted); + + /// + /// Operation was rejected because the system is not in a state required for the operation's + /// execution. For example, directory to be deleted may be non-empty, an rmdir operation is + /// applied to a non-directory, etc. + /// A litmus test that may help a service implementor in deciding between FAILED_PRECONDITION, + /// ABORTED, and UNAVAILABLE: (a) Use UNAVAILABLE if the client can retry just the failing call. + /// (b) Use ABORTED if the client should retry at a higher-level (e.g., restarting a + /// read-modify-write sequence). (c) Use FAILED_PRECONDITION if the client should not retry until + /// the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory + /// is non-empty, FAILED_PRECONDITION should be returned since the client should not retry unless + /// they have first fixed up the directory by deleting files from it. + /// + public static readonly Status FailedPrecondition = new Status(CanonicalCode.FailedPrecondition); + + /// + /// The operation was aborted, typically due to a concurrency issue like sequencer check + /// failures, transaction aborts, etc. + /// + public static readonly Status Aborted = new Status(CanonicalCode.Aborted); + + /// + /// Operation was attempted past the valid range. E.g., seeking or reading past end of file. + /// + /// Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system + /// state changes. For example, a 32-bit file system will generate INVALID_ARGUMENT if asked to + /// read at an offset that is not in the range [0,2^32-1], but it will generate OUT_OF_RANGE if + /// asked to read from an offset past the current file size. + /// + /// There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE. We recommend + /// using OUT_OF_RANGE (the more specific error) when it applies so that callers who are + /// iterating through a space can easily look for an OUT_OF_RANGE error to detect when they are + /// done. + /// + public static readonly Status OutOfRange = new Status(CanonicalCode.OutOfRange); + + /// + /// Operation is not implemented or not supported/enabled in this service. + /// + public static readonly Status Unimplemented = new Status(CanonicalCode.Unimplemented); + + /// + /// Internal errors. Means some invariants expected by underlying system has been broken. If you + /// see one of these errors, something is very broken. + /// + public static readonly Status Internal = new Status(CanonicalCode.Internal); + + /// + /// The service is currently unavailable. This is a most likely a transient condition and may be + /// corrected by retrying with a backoff. + /// + /// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE. + /// + public static readonly Status Unavailable = new Status(CanonicalCode.Unavailable); + + /// + /// Unrecoverable data loss or corruption. + /// + public static readonly Status DataLoss = new Status(CanonicalCode.DataLoss); + + internal Status(CanonicalCode canonicalCode, string description = null) + { + this.CanonicalCode = canonicalCode; + this.Description = description; + } + + /// + /// Gets the canonical code from this status. + /// + public CanonicalCode CanonicalCode { get; } + + /// + /// Gets the status description. + /// + public string Description { get; } + + /// + /// Gets a value indicating whether span completed sucessfully. + /// + public bool IsOk + { + get + { + return this.CanonicalCode == CanonicalCode.Ok; + } + } + + /// + /// Returns a new instance of a status with the description populated. + /// + /// Description of the status. + /// New instance of the status class with the description populated. + public Status WithDescription(string description) + { + if (this.Description == description) + { + return this; + } + + return new Status(this.CanonicalCode, description); + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj is Status)) + { + return false; + } + + Status that = (Status)obj; + return this.CanonicalCode == that.CanonicalCode && this.Description == that.Description; + } + + /// + public override int GetHashCode() + { + int result = 1; + result = (31 * result) + this.CanonicalCode.GetHashCode(); + result = (31 * result) + this.Description.GetHashCode(); + return result; + } + + /// + public override string ToString() + { + return "Status{" + + "canonicalCode=" + this.CanonicalCode + ", " + + "description=" + this.Description + + "}"; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/TraceId.cs b/src/OpenCensus.Abstractions/Trace/TraceId.cs new file mode 100644 index 000000000..07afb265d --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/TraceId.cs @@ -0,0 +1,188 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using OpenCensus.Utils; + + public sealed class TraceId : ITraceId + { + public const int Size = 16; + private static readonly TraceId InvalidTraceId = new TraceId(new byte[Size]); + + private readonly byte[] bytes; + + private TraceId(byte[] bytes) + { + this.bytes = bytes; + } + + public static ITraceId Invalid + { + get + { + return InvalidTraceId; + } + } + + public byte[] Bytes + { + get + { + byte[] copyOf = new byte[Size]; + Buffer.BlockCopy(this.bytes, 0, copyOf, 0, Size); + return copyOf; + } + } + + public bool IsValid + { + get + { + return !Arrays.Equals(this.bytes, InvalidTraceId.bytes); + } + } + + public long LowerLong + { + get + { + long result = 0; + for (int i = 0; i < 8; i++) + { + result <<= 8; +#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand + result |= this.bytes[i] & 0xff; +#pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand + } + + if (result < 0) + { + return -result; + } + + return result; + } + } + + public static ITraceId FromBytes(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (buffer.Length != Size) + { + throw new ArgumentOutOfRangeException(string.Format("Invalid size: expected {0}, got {1}", Size, buffer.Length)); + } + + byte[] bytesCopied = new byte[Size]; + Buffer.BlockCopy(buffer, 0, bytesCopied, 0, Size); + return new TraceId(bytesCopied); + } + + public static ITraceId FromBytes(byte[] src, int srcOffset) + { + byte[] bytes = new byte[Size]; + Buffer.BlockCopy(src, srcOffset, bytes, 0, Size); + return new TraceId(bytes); + } + + public static ITraceId FromLowerBase16(string src) + { + if (src.Length != 2 * Size) + { + throw new ArgumentOutOfRangeException(string.Format("Invalid size: expected {0}, got {1}", 2 * Size, src.Length)); + } + + byte[] bytes = Arrays.StringToByteArray(src); + return new TraceId(bytes); + } + + public static ITraceId GenerateRandomId(IRandomGenerator random) + { + byte[] bytes = new byte[Size]; + do + { + random.NextBytes(bytes); + } + while (Arrays.Equals(bytes, InvalidTraceId.bytes)); + return new TraceId(bytes); + } + + public void CopyBytesTo(byte[] dest, int destOffset) + { + Buffer.BlockCopy(this.bytes, 0, dest, destOffset, Size); + } + + public string ToLowerBase16() + { + var bytes = this.Bytes; + var result = Arrays.ByteArrayToString(bytes); + return result; + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj is TraceId)) + { + return false; + } + + TraceId that = (TraceId)obj; + return Arrays.Equals(this.bytes, that.bytes); + } + + /// + public override int GetHashCode() + { + return Arrays.GetHashCode(this.bytes); + } + + /// + public override string ToString() + { + return "TraceId{" + + "bytes=" + this.ToLowerBase16() + + "}"; + } + + public int CompareTo(ITraceId other) + { + TraceId that = other as TraceId; + for (int i = 0; i < Size; i++) + { + if (this.bytes[i] != that.bytes[i]) + { + sbyte b1 = (sbyte)this.bytes[i]; + sbyte b2 = (sbyte)that.bytes[i]; + + return b1 < b2 ? -1 : 1; + } + } + + return 0; + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/TraceOptions.cs b/src/OpenCensus.Abstractions/Trace/TraceOptions.cs new file mode 100644 index 000000000..2fdd39994 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/TraceOptions.cs @@ -0,0 +1,200 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + /// + /// Trace options. + /// + public sealed class TraceOptions + { + /// + /// Size of trace options flag. + /// + public const int Size = 1; + + /// + /// Default trace options. Nothing set. + /// + public static readonly TraceOptions Default = new TraceOptions(DefaultOptions); + + /// + /// Sampled trace options. + /// + public static readonly TraceOptions Sampled = new TraceOptions(1); + + internal const byte DefaultOptions = 0; + + internal const byte IsSampledBit = 0x1; + + private byte options; + + internal TraceOptions(byte options) + { + this.options = options; + } + + /// + /// Gets the bytes representation of a trace options. + /// + public byte[] Bytes + { + get + { + byte[] bytes = new byte[Size]; + bytes[0] = this.options; + return bytes; + } + } + + /// + /// Gets a value indicating whether span is sampled or not. + /// + public bool IsSampled + { + get + { + return this.HasOption(IsSampledBit); + } + } + + internal sbyte Options + { + get { return (sbyte)this.options; } + } + + /// + /// Deserializes trace options from bytes. + /// + /// Buffer to deserialize. + /// Trace options deserialized from the bytes array. + public static TraceOptions FromBytes(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (buffer.Length != Size) + { + throw new ArgumentOutOfRangeException(string.Format("Invalid size: expected {0}, got {1}", Size, buffer.Length)); + } + + byte[] bytesCopied = new byte[Size]; + Buffer.BlockCopy(buffer, 0, bytesCopied, 0, Size); + return new TraceOptions(bytesCopied[0]); + } + + /// + /// Trace options from bytes with the given offset. + /// + /// Buffer to sdeserialize trace optiosn from. + /// Buffer offset. + /// Trace options deserialized from the buffer. + public static TraceOptions FromBytes(byte[] src, int srcOffset) + { + if (srcOffset < 0 || srcOffset >= src.Length) + { + throw new IndexOutOfRangeException("srcOffset"); + } + + return new TraceOptions(src[srcOffset]); + } + + /// + /// Gets the trace options builder. + /// + /// Trace options builder. + public static TraceOptionsBuilder Builder() + { + return new TraceOptionsBuilder(DefaultOptions); + } + + /// + /// Trace options builder pre-initialized from the given trace options instance. + /// + /// Trace options to pre-initialize the builder. + /// Trace options builder. + public static TraceOptionsBuilder Builder(TraceOptions traceOptions) + { + return new TraceOptionsBuilder(traceOptions.options); + } + + /// + /// Serializes trace options into bytes array at a given offset. + /// + /// Destination to serialize value to. + /// Destintion offset. + public void CopyBytesTo(byte[] dest, int destOffset) + { + if (destOffset < 0 || destOffset >= dest.Length) + { + throw new IndexOutOfRangeException("destOffset"); + } + + dest[destOffset] = this.options; + } + + /// + public override bool Equals(object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj is TraceOptions)) + { + return false; + } + + TraceOptions that = (TraceOptions)obj; + return this.options == that.options; + } + + /// + public override int GetHashCode() + { + int result = (31 * 1) + this.options; + return result; + } + + /// + public override string ToString() + { + return "TraceOptions{" + + "sampled=" + this.IsSampled + + "}"; + } + + private bool HasOption(int mask) + { + return (this.options & mask) != 0; + } + + private void ClearOption(int mask) + { + this.options = (byte)(this.options & ~mask); + } + + private void SetOption(int mask) + { + this.options = (byte)(this.options | mask); + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/TraceOptionsBuilder.cs b/src/OpenCensus.Abstractions/Trace/TraceOptionsBuilder.cs new file mode 100644 index 000000000..289f9342f --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/TraceOptionsBuilder.cs @@ -0,0 +1,64 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + /// + /// Trace options builder. + /// + public class TraceOptionsBuilder + { + private byte options; + + internal TraceOptionsBuilder() + : this(TraceOptions.DefaultOptions) + { + } + + internal TraceOptionsBuilder(byte options) + { + this.options = options; + } + + /// + /// Sets is sampled flag. + /// + /// New value for the isSampled flag. + /// This builder for operations chaining. + public TraceOptionsBuilder SetIsSampled(bool isSampled) + { + if (isSampled) + { + this.options = (byte)(this.options | TraceOptions.IsSampledBit); + } + else + { + this.options = (byte)(this.options & ~TraceOptions.IsSampledBit); + } + + return this; + } + + /// + /// Builds span options from the values provided. + /// + /// Span options built by this builder. + public TraceOptions Build() + { + return new TraceOptions(this.options); + } + } +} diff --git a/src/OpenCensus.Abstractions/Trace/TraceState.cs b/src/OpenCensus.Abstractions/Trace/TraceState.cs new file mode 100644 index 000000000..531af4b67 --- /dev/null +++ b/src/OpenCensus.Abstractions/Trace/TraceState.cs @@ -0,0 +1,346 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Tracestate entries allowing different vendors to participate in a trace. + /// See https://github.com/w3c/distributed-tracing. + /// + public sealed class Tracestate + { + /// + /// An instance of empty tracestate. + /// + public static readonly Tracestate Empty = new Tracestate(Enumerable.Empty()); + + private const int KeyMaxSize = 256; + private const int ValueMaxSize = 256; + private const int MaxKeyValuePairsCount = 32; + + private readonly IEnumerable entries; + + private Tracestate(IEnumerable entries) + { + this.entries = entries; + } + + /// + /// Gets the tracestate builder. + /// + public static TracestateBuilder Builder + { + get + { + return new TracestateBuilder(Tracestate.Empty); + } + } + + /// + /// Gets the list of entris in tracestate. + /// + public IEnumerable Entries { get => this.entries; } + + /// + /// Returns the value to which the specified key is mapped, or null if this map contains no mapping + /// for the key. + /// + /// Key with which the specified value is to be associated. + /// + /// the value to which the specified key is mapped, or null if this map contains no mapping + /// for the key. + /// + public string Get(string key) + { + foreach (Entry entry in this.Entries) + { + if (entry.Key.Equals(key)) + { + return entry.Value; + } + } + + return null; + } + + /// + /// Gets the builder to create derived tracestate. + /// + /// Tracestate builder. + public TracestateBuilder ToBuilder() + { + return new TracestateBuilder(this); + } + + private static bool ValidateKey(string key) + { + // Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter, and + // can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, + // forward slashes / and @ + + int i = 0; + + if (string.IsNullOrEmpty(key) + || key.Length > KeyMaxSize + || key[i] < 'a' + || key[i] > 'z') + { + return false; + } + + // before + for (i = 1; i < key.Length; i++) + { + char c = key[i]; + + if (c == '@') + { + // vendor follows + break; + } + + if (!(c >= 'a' && c <= 'z') + && !(c >= '0' && c <= '9') + && c != '_' + && c != '-' + && c != '*' + && c != '/') + { + return false; + } + } + + i++; // skip @ or increment further than key.Length + + var vendorLength = key.Length - i; + if (vendorLength == 0 || vendorLength > 14) + { + // vendor name should be at least 1 to 14 character long + return false; + } + + if (vendorLength > 0) + { + if (i > 242) + { + // tenant section should be less than 241 characters long + return false; + } + } + + for (; i < key.Length; i++) + { + char c = key[i]; + + if (!(c >= 'a' && c <= 'z') + && !(c >= '0' && c <= '9') + && c != '_' + && c != '-' + && c != '*' + && c != '/') + { + return false; + } + } + + return true; + } + + private static bool ValidateValue(string value) + { + // Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the range + // 0x20 to 0x7E) except comma , and =. + + if (value.Length > ValueMaxSize || value[value.Length - 1] == ' ' /* '\u0020' */) + { + return false; + } + + for (int i = 0; i < value.Length; i++) + { + char c = value[i]; + + if (c == ',' || c == '=' || c < ' ' /* '\u0020' */ || c > '~' /* '\u007E' */) + { + return false; + } + } + + return true; + } + + private static Tracestate Create(ICollection entries) + { + // TODO: discard last entries instead of throwing + + if (entries.Count > MaxKeyValuePairsCount) + { + throw new ArgumentException("Too many entries.", nameof(entries)); + } + + return new Tracestate(entries); + } + + /// + /// Immutable tracestate entry. + /// + public sealed class Entry + { + private readonly string key; + private readonly string value; + + private Entry(string key, string value) + { + this.key = key; + this.value = value; + } + + /// + /// Gets the key of tracestate entry. + /// + public string Key { get => this.key; } + + /// + /// Gets the value of tracestate entry. + /// + public string Value { get => this.value; } + + /// + /// Creates a new Entry with the given name and value. + /// + /// Key of tracestate entry. + /// Value of thacestate entry. + /// The new tracestate entry. + public static Entry Create(string key, string value) + { + key = key ?? throw new ArgumentNullException(nameof(key)); + value = value ?? throw new ArgumentNullException(nameof(value)); + + if (!ValidateKey(key)) + { + throw new ArgumentException("Doesn't comply to spec", nameof(key)); + } + + if (!ValidateValue(value)) + { + throw new ArgumentException("Doesn't comply to spec", nameof(value)); + } + + return new Entry(key, value); + } + } + + /// + /// Tracestate builder. + /// + public sealed class TracestateBuilder + { + private readonly Tracestate parent; + + private IList entries; + + internal TracestateBuilder(Tracestate parent) + { + parent = parent ?? throw new ArgumentNullException(nameof(parent)); + + this.parent = parent; + this.entries = null; + } + + /// + /// Adds or updates the entry for the given key. + /// New or updated entry will be moved to the front of the list. + /// + /// Key to update value for. + /// Value set for the key. + /// Tracestate builder for chained calls. + public TracestateBuilder Set(string key, string value) + { + // Initially create the Entry to validate input. + + Entry entry = Entry.Create(key, value); + + if (this.entries == null) + { + // Copy entries from the parent. + this.entries = new List(this.parent.Entries); + } + + for (int i = 0; i < this.entries.Count; i++) + { + if (this.entries[i].Key.Equals(entry.Key)) + { + this.entries.RemoveAt(i); + + // Exit now because the entries list cannot contain duplicates. + break; + } + } + + // Inserts the element at the front of this list. + this.entries.Insert(0, entry); + return this; + } + + /// + /// Removes entry for the given key. + /// + /// Key to remove from tracestate. + /// Tracestate builder for chained calls. + public TracestateBuilder Remove(string key) + { + key = key ?? throw new ArgumentNullException(nameof(key)); + + if (this.entries == null) + { + // Copy entries from the parent. + this.entries = new List(this.parent.Entries); + } + + for (int i = 0; i < this.entries.Count; i++) + { + if (this.entries[i].Key.Equals(key)) + { + this.entries.RemoveAt(i); + + // Exit now because the entries list cannot contain duplicates. + break; + } + } + + return this; + } + + /// + /// Builds the tracestate. + /// + /// Resulting tracestate. + public Tracestate Build() + { + if (this.entries == null) + { + return this.parent; + } + + return Tracestate.Create(this.entries); + } + } + } +} diff --git a/src/OpenCensus.Abstractions/Utils/Arrays.cs b/src/OpenCensus.Abstractions/Utils/Arrays.cs new file mode 100644 index 000000000..c6c2cf782 --- /dev/null +++ b/src/OpenCensus.Abstractions/Utils/Arrays.cs @@ -0,0 +1,145 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System; + using System.Text; + + internal static class Arrays + { + private static readonly uint[] ByteToHexLookupTable = CreateLookupTable(); + + public static bool Equals(byte[] array1, byte[] array2) + { + if (array1 == array2) + { + return true; + } + + if (array1 == null || array2 == null) + { + return false; + } + + if (array2.Length != array1.Length) + { + return false; + } + + for (int i = 0; i < array1.Length; i++) + { + if (array1[i] != array2[i]) + { + return false; + } + } + + return true; + } + + internal static int GetHashCode(byte[] array) + { + if (array == null) + { + return 0; + } + + int result = 1; + foreach (byte b in array) + { + result = (31 * result) + b; + } + + return result; + } + + internal static int HexCharToInt(char c) + { + if ((c >= '0') && (c <= '9')) + { + return c - '0'; + } + + if ((c >= 'a') && (c <= 'f')) + { + return c - 'a' + 10; + } + + if ((c >= 'A') && (c <= 'F')) + { + return c - 'A' + 10; + } + + throw new ArgumentOutOfRangeException("Invalid character: " + c); + } + + // https://stackoverflow.com/a/24343727 + internal static uint[] CreateLookupTable() + { + uint[] table = new uint[256]; + for (int i = 0; i < 256; i++) + { + string s = i.ToString("x2"); + table[i] = (uint)s[0]; + table[i] += (uint)s[1] << 16; + } + + return table; + } + + // https://stackoverflow.com/a/24343727 + internal static char[] ByteToHexCharArray(byte b) + { + char[] result = new char[2]; + + result[0] = (char)ByteToHexLookupTable[b]; + result[1] = (char)(ByteToHexLookupTable[b] >> 16); + + return result; + } + + internal static byte[] StringToByteArray(string src, int start = 0, int len = -1) + { + if (len == -1) + { + len = src.Length; + } + + int size = len / 2; + byte[] bytes = new byte[size]; + for (int i = 0, j = start; i < size; i++) + { + int high = HexCharToInt(src[j++]); + int low = HexCharToInt(src[j++]); + bytes[i] = (byte)(high << 4 | low); + } + + return bytes; + } + + internal static string ByteArrayToString(byte[] bytes) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.Length; i++) + { + sb.Append(ByteToHexCharArray(bytes[i])); + } + + return sb.ToString(); + } + } +} diff --git a/src/OpenCensus.Abstractions/Utils/CanonicalCodeExtensions.cs b/src/OpenCensus.Abstractions/Utils/CanonicalCodeExtensions.cs new file mode 100644 index 000000000..4bd8f5027 --- /dev/null +++ b/src/OpenCensus.Abstractions/Utils/CanonicalCodeExtensions.cs @@ -0,0 +1,28 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using OpenCensus.Trace; + + internal static class CanonicalCodeExtensions + { + public static Status ToStatus(this CanonicalCode code) + { + return new Status(code); + } + } +} diff --git a/src/OpenCensus.Abstractions/Utils/Collections.cs b/src/OpenCensus.Abstractions/Utils/Collections.cs new file mode 100644 index 000000000..36701bc3b --- /dev/null +++ b/src/OpenCensus.Abstractions/Utils/Collections.cs @@ -0,0 +1,71 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Abstractions.Utils +{ + using System.Collections.Generic; + using System.Linq; + using System.Text; + + internal static class Collections + { + public static string ToString(IDictionary dict) + { + if (dict == null) + { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + foreach (var kvp in dict) + { + sb.Append(kvp.Key.ToString()); + sb.Append("="); + sb.Append(kvp.Value.ToString()); + sb.Append(" "); + } + + return sb.ToString(); + } + + public static string ToString(IEnumerable list) + { + if (list == null) + { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + foreach (var val in list) + { + if (val != null) + { + sb.Append(val.ToString()); + sb.Append(" "); + } + } + + return sb.ToString(); + } + + public static bool AreEquivalent(IEnumerable c1, IEnumerable c2) + { + var c1Dist = c1.Distinct(); + var c2Dist = c2.Distinct(); + return c1.Count() == c2.Count() && c1Dist.Count() == c2Dist.Count() && c1Dist.Intersect(c2Dist).Count() == c1Dist.Count(); + } + } +} diff --git a/src/OpenCensus.Abstractions/Utils/DoubleUtil.cs b/src/OpenCensus.Abstractions/Utils/DoubleUtil.cs new file mode 100644 index 000000000..e12000d11 --- /dev/null +++ b/src/OpenCensus.Abstractions/Utils/DoubleUtil.cs @@ -0,0 +1,56 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System; + + internal static class DoubleUtil + { + public static long ToInt64(double arg) + { + if (double.IsPositiveInfinity(arg)) + { + return 0x7ff0000000000000L; + } + + if (double.IsNegativeInfinity(arg)) + { + unchecked + { + return (long)0xfff0000000000000L; + } + } + + if (double.IsNaN(arg)) + { + return 0x7ff8000000000000L; + } + + if (arg == double.MaxValue) + { + return long.MaxValue; + } + + if (arg == double.MinValue) + { + return long.MinValue; + } + + return Convert.ToInt64(arg); + } + } +} diff --git a/src/OpenCensus.Abstractions/Utils/IElement.cs b/src/OpenCensus.Abstractions/Utils/IElement.cs new file mode 100644 index 000000000..64739d21f --- /dev/null +++ b/src/OpenCensus.Abstractions/Utils/IElement.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + /// + /// Element of double linked list. + /// + /// Type of a stored value. + public interface IElement + where T : IElement + { + /// + /// Gets or sets next element. + /// + T Next { get; set; } + + /// + /// Gets or sets previous element. + /// + T Previous { get; set; } + } +} diff --git a/src/OpenCensus.Collector.AspNetCore/AspNetCoreCollectorEventSource.cs b/src/OpenCensus.Collector.AspNetCore/AspNetCoreCollectorEventSource.cs new file mode 100644 index 000000000..ba0f02759 --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/AspNetCoreCollectorEventSource.cs @@ -0,0 +1,74 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Tracing; + using System.Globalization; + using System.Text; + using System.Threading; + + /// + /// EventSource listing ETW events emitted from the project. + /// + [EventSource(Name = "OpenCensus.Collector.AspNetCore")] + internal class AspNetCoreCollectorEventSource : EventSource + { + internal static AspNetCoreCollectorEventSource Log = new AspNetCoreCollectorEventSource(); + + [NonEvent] + public void ExceptionInCustomSampler(Exception ex) + { + if (Log.IsEnabled(EventLevel.Warning, EventKeywords.All)) + { + this.ExceptionInCustomSampler(ToInvariantString(ex)); + } + } + + [Event(1, Message = "Context is NULL in end callback. Span will not be recorded.", Level = EventLevel.Warning)] + public void NullContext() + { + this.WriteEvent(1); + } + + [Event(2, Message = "Error getting custom sampler, the default sampler will be used. Exception : {0}", Level = EventLevel.Warning)] + public void ExceptionInCustomSampler(string ex) + { + this.WriteEvent(2, ex); + } + + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + private static string ToInvariantString(Exception exception) + { + CultureInfo originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/src/OpenCensus.Collector.AspNetCore/AssemblyInfo.cs b/src/OpenCensus.Collector.AspNetCore/AssemblyInfo.cs new file mode 100644 index 000000000..6a81089a0 --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/AssemblyInfo.cs @@ -0,0 +1,17 @@ +// +// Copyright 2018, OpenCensus 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. +// + +[assembly: System.CLSCompliant(true)] diff --git a/src/OpenCensus.Collector.AspNetCore/Implementation/HttpInListener.cs b/src/OpenCensus.Collector.AspNetCore/Implementation/HttpInListener.cs new file mode 100644 index 000000000..675a8467d --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/Implementation/HttpInListener.cs @@ -0,0 +1,178 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore.Implementation +{ + using System; + using System.Diagnostics; + using System.Linq; + using System.Text; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Features; + using OpenCensus.Collector.AspNetCore.Common; + using OpenCensus.Trace; + using OpenCensus.Trace.Propagation; + + internal class HttpInListener : ListenerHandler + { + private const string UnknownHostName = "UNKNOWN-HOST"; + private readonly PropertyFetcher startContextFetcher = new PropertyFetcher("HttpContext"); + private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext"); + private readonly PropertyFetcher beforeActionActionDescriptorFetcher = new PropertyFetcher("actionDescriptor"); + private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new PropertyFetcher("AttributeRouteInfo"); + private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template"); + private readonly IPropagationComponent propagationComponent; + + public HttpInListener(ITracer tracer, Func samplerFactory, IPropagationComponent propagationComponent) + : base("Microsoft.AspNetCore", tracer, samplerFactory) + { + this.propagationComponent = propagationComponent; + } + + public override void OnStartActivity(Activity activity, object payload) + { + var context = this.startContextFetcher.Fetch(payload) as HttpContext; + + if (context == null) + { + // Debug.WriteLine("context is null"); + return; + } + + HttpRequest request = context.Request; + + var ctx = this.propagationComponent.TextFormat.Extract( + request, + (r, name) => r.Headers[name]); + + // see the spec https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/HTTP.md + + string path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/"; + + ISpan span = null; + this.Tracer.SpanBuilderWithRemoteParent(path, SpanKind.Server, ctx).SetSampler(this.SamplerFactory(request)).StartScopedSpan(out span); + if (span == null) + { + // Debug.WriteLine("span is null"); + return; + } + + // Note, route is missing at this stage. It will be available later + + span.PutHttpHostAttribute(request.Host.Host, request.Host.Port ?? 80); + span.PutHttpMethodAttribute(request.Method); + span.PutHttpPathAttribute(path); + + var userAgent = request.Headers["User-Agent"].FirstOrDefault(); + span.PutHttpUserAgentAttribute(userAgent); + span.PutHttpRawUrlAttribute(GetUri(request)); + } + + public override void OnStopActivity(Activity activity, object payload) + { + var context = this.stopContextFetcher.Fetch(payload) as HttpContext; + + if (context == null) + { + AspNetCoreCollectorEventSource.Log.NullContext(); + return; + } + + var span = this.Tracer.CurrentSpan; + + if (span == null) + { + // TODO: report lost span + return; + } + + var response = context.Response; + + span.PutHttpStatusCode(response.StatusCode, response.HttpContext.Features.Get().ReasonPhrase); + span.End(); + } + + public override void OnCustom(string name, Activity activity, object payload) + { + if (name == "Microsoft.AspNetCore.Mvc.BeforeAction") + { + var span = this.Tracer.CurrentSpan; + + if (span == null) + { + // TODO: report lost span + return; + } + + // See https://github.com/aspnet/Mvc/blob/2414db256f32a047770326d14d8b0e2afd49ba49/src/Microsoft.AspNetCore.Mvc.Core/MvcCoreDiagnosticSourceExtensions.cs#L36-L44 + // Reflection accessing: ActionDescriptor.AttributeRouteInfo.Template + // The reason to use reflection is to avoid a reference on MVC package. + // This package can be used with non-MVC apps and this logic simply wouldn't run. + // Taking reference on MVC will increase size of deployment for non-MVC apps. + var actionDescriptor = this.beforeActionActionDescriptorFetcher.Fetch(payload); + var attributeRouteInfo = this.beforeActionAttributeRouteInfoFetcher.Fetch(actionDescriptor); + var template = this.beforeActionTemplateFetcher.Fetch(attributeRouteInfo) as string; + + if (!string.IsNullOrEmpty(template)) + { + // override the span name that was previously set to the path part of URL. + span.Name = template; + + span.PutHttpRouteAttribute(template); + } + + // TODO: Should we get values from RouteData? + // private readonly PropertyFetcher beforActionRouteDataFetcher = new PropertyFetcher("routeData"); + // var routeData = this.beforActionRouteDataFetcher.Fetch(payload) as RouteData; + } + } + + private static string GetUri(HttpRequest request) + { + var builder = new StringBuilder(); + + builder.Append(request.Scheme).Append("://"); + + if (request.Host.HasValue) + { + builder.Append(request.Host.Value); + } + else + { + // HTTP 1.0 request with NO host header would result in empty Host. + // Use placeholder to avoid incorrect URL like "http:///" + builder.Append(UnknownHostName); + } + + if (request.PathBase.HasValue) + { + builder.Append(request.PathBase.Value); + } + + if (request.Path.HasValue) + { + builder.Append(request.Path.Value); + } + + if (request.QueryString.HasValue) + { + builder.Append(request.QueryString); + } + + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceListener.cs b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceListener.cs new file mode 100644 index 000000000..a6fe673a9 --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceListener.cs @@ -0,0 +1,83 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore.Common +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + internal class DiagnosticSourceListener : IObserver>, IDisposable + { + private readonly string sourceName; + private readonly ListenerHandler handler; + + public DiagnosticSourceListener(string sourceName, ListenerHandler handler) + { + this.sourceName = sourceName; + this.handler = handler; + } + + public IDisposable Subscription { get; set; } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + if (Activity.Current == null) + { + Debug.WriteLine("Activity is null " + value.Key); + return; + } + + try + { + if (value.Key.EndsWith("Start")) + { + this.handler.OnStartActivity(Activity.Current, value.Value); + } + else if (value.Key.EndsWith("Stop")) + { + this.handler.OnStopActivity(Activity.Current, value.Value); + } + else if (value.Key.EndsWith("Exception")) + { + this.handler.OnException(Activity.Current, value.Value); + } + else + { + this.handler.OnCustom(value.Key, Activity.Current, value.Value); + } + } + catch (Exception e) + { + // Debug.WriteLine(e); + // TODO: make sure to output the handler name as part of error message + } + } + + public void Dispose() + { + this.Subscription?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceSubscriber.cs b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceSubscriber.cs new file mode 100644 index 000000000..184623b6a --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/DiagnosticSourceSubscriber.cs @@ -0,0 +1,96 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore.Common +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Diagnostics; + using System.Threading; + using Microsoft.AspNetCore.Http; + using OpenCensus.Trace; + + internal class DiagnosticSourceSubscriber : IDisposable, IObserver + { + private readonly Dictionary, ListenerHandler>> handlers; + private readonly ITracer tracer; + private readonly Func sampler; + private ConcurrentDictionary subscriptions; + private bool disposing; + private IDisposable subscription; + + public DiagnosticSourceSubscriber(Dictionary, ListenerHandler>> handlers, ITracer tracer, Func sampler) + { + this.subscriptions = new ConcurrentDictionary(); + this.handlers = handlers; + this.tracer = tracer; + this.sampler = sampler; + } + + public void Subscribe() + { + if (this.subscription == null) + { + this.subscription = DiagnosticListener.AllListeners.Subscribe(this); + } + } + + public void OnNext(DiagnosticListener value) + { + if (!Volatile.Read(ref this.disposing) && this.subscriptions != null) + { + if (this.handlers.ContainsKey(value.Name)) + { + this.subscriptions.GetOrAdd(value.Name, name => + { + var dl = new DiagnosticSourceListener(value.Name, this.handlers[value.Name](this.tracer, this.sampler)); + dl.Subscription = value.Subscribe(dl); + return dl; + }); + } + } + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void Dispose() + { + Volatile.Write(ref this.disposing, true); + + var subsCopy = this.subscriptions; + this.subscriptions = null; + + var keys = subsCopy.Keys; + foreach (var key in keys) + { + if (subsCopy.TryRemove(key, out var sub)) + { + sub?.Dispose(); + } + } + + this.subscription?.Dispose(); + this.subscription = null; + } + } +} diff --git a/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/ListenerHandler.cs b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/ListenerHandler.cs new file mode 100644 index 000000000..fbb7eb464 --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/ListenerHandler.cs @@ -0,0 +1,69 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore.Common +{ + using System; + using System.Diagnostics; + using Microsoft.AspNetCore.Http; + using OpenCensus.Trace; + + internal abstract class ListenerHandler + { + protected readonly ITracer Tracer; + + protected readonly Func SamplerFactory; + + public ListenerHandler(string sourceName, ITracer tracer, Func samplerFactory) + { + this.SourceName = sourceName; + this.Tracer = tracer; + this.SamplerFactory = samplerFactory; + } + + public string SourceName { get; } + + public abstract void OnStartActivity(Activity activity, object payload); + + public virtual void OnStopActivity(Activity activity, object payload) + { + var span = this.Tracer.CurrentSpan; + + if (span == null) + { + // TODO: Notify that span got lost + return; + } + + foreach (var tag in activity.Tags) + { + span.PutAttribute(tag.Key, AttributeValue.StringAttributeValue(tag.Value)); + } + } + + public virtual void OnException(Activity activity, object payload) + { + var span = this.Tracer.CurrentSpan; + + // TODO: gather exception information + } + + public virtual void OnCustom(string name, Activity activity, object payload) + { + // if custom handler needs to react on other events - this method should be overridden + } + } +} diff --git a/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/PropertyFetcher.cs b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/PropertyFetcher.cs new file mode 100644 index 000000000..03df607f6 --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/Implementation/OpenCensus.Collector.AspNetCore.Common/PropertyFetcher.cs @@ -0,0 +1,94 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore.Common +{ + using System; + using System.Reflection; + + internal class PropertyFetcher + { + private readonly string propertyName; + private PropertyFetch innerFetcher; + + public PropertyFetcher(string propertyName) + { + this.propertyName = propertyName; + } + + public object Fetch(object obj) + { + if (this.innerFetcher == null) + { + var type = obj.GetType().GetTypeInfo(); + var property = type.GetDeclaredProperty(this.propertyName); + if (property == null) + { + property = type.GetProperty(this.propertyName); + } + + this.innerFetcher = PropertyFetch.FetcherForProperty(property); + } + + return this.innerFetcher?.Fetch(obj); + } + + // see https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs + private class PropertyFetch + { + /// + /// Create a property fetcher from a .NET Reflection PropertyInfo class that + /// represents a property of a particular type. + /// + public static PropertyFetch FetcherForProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + // returns null on any fetch. + return new PropertyFetch(); + } + + var typedPropertyFetcher = typeof(TypedFetchProperty<,>); + var instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( + propertyInfo.DeclaringType, propertyInfo.PropertyType); + return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, propertyInfo); + } + + /// + /// Given an object, fetch the property that this propertyFetch represents. + /// + public virtual object Fetch(object obj) + { + return null; + } + + private class TypedFetchProperty : PropertyFetch + { + private readonly Func propertyFetch; + + public TypedFetchProperty(PropertyInfo property) + { + this.propertyFetch = (Func)property.GetMethod.CreateDelegate(typeof(Func)); + } + + public override object Fetch(object obj) + { + return this.propertyFetch((TObject)obj); + } + } + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Collector.AspNetCore/OpenCensus.Collector.AspNetCore.csproj b/src/OpenCensus.Collector.AspNetCore/OpenCensus.Collector.AspNetCore.csproj new file mode 100644 index 000000000..c623a4a90 --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/OpenCensus.Collector.AspNetCore.csproj @@ -0,0 +1,29 @@ + + + + + netstandard2.0 + netstandard2.0 + True + + + + OpenCensus collector for ASP.NET Core requests + Tracing;OpenCensus;Management;Monitoring;distributed-tracing;AspNetCore + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + + + All + + + + + + diff --git a/src/OpenCensus.Collector.AspNetCore/RequestsCollector.cs b/src/OpenCensus.Collector.AspNetCore/RequestsCollector.cs new file mode 100644 index 000000000..d5d5220be --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/RequestsCollector.cs @@ -0,0 +1,74 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Threading; + using Microsoft.AspNetCore.Http; + using OpenCensus.Collector.AspNetCore.Common; + using OpenCensus.Collector.AspNetCore.Implementation; + using OpenCensus.Trace; + using OpenCensus.Trace.Propagation; + + /// + /// Dependencies collector. + /// + public class RequestsCollector : IDisposable + { + private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration options for dependencies collector. + /// Tracer to record traced with. + /// Sampler to use to sample dependency calls. + /// Wire context propagation component. + public RequestsCollector(RequestsCollectorOptions options, ITracer tracer, ISampler sampler, IPropagationComponent propagationComponent) + { + this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber( + new Dictionary, ListenerHandler>>() + { + { "Microsoft.AspNetCore", (t, s) => new HttpInListener(t, s, propagationComponent) }, + }, + tracer, + x => + { + ISampler s = null; + try + { + s = options.CustomSampler(x); + } + catch (Exception e) + { + s = null; + AspNetCoreCollectorEventSource.Log.ExceptionInCustomSampler(e); + } + + return s == null ? sampler : s; + }); + this.diagnosticSourceSubscriber.Subscribe(); + } + + public void Dispose() + { + this.diagnosticSourceSubscriber.Dispose(); + } + } +} diff --git a/src/OpenCensus.Collector.AspNetCore/RequestsCollectorOptions.cs b/src/OpenCensus.Collector.AspNetCore/RequestsCollectorOptions.cs new file mode 100644 index 000000000..967d7b8ed --- /dev/null +++ b/src/OpenCensus.Collector.AspNetCore/RequestsCollectorOptions.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore +{ + using System; + using Microsoft.AspNetCore.Http; + using OpenCensus.Trace; + using OpenCensus.Trace.Sampler; + + /// + /// Options for dependencies collector. + /// + public class RequestsCollectorOptions + { + private static Func defaultSampler = (req) => { return null; }; + + /// + /// Initializes a new instance of the class. + /// + /// Custom sampling function, if any + public RequestsCollectorOptions(Func sampler = null) + { + this.CustomSampler = sampler ?? defaultSampler; + } + + /// + /// Gets a hook to exclude calls based on domain + /// or other per-request criterion. + /// + public Func CustomSampler { get; private set; } + } +} diff --git a/src/OpenCensus.Collector.Dependencies/AssemblyInfo.cs b/src/OpenCensus.Collector.Dependencies/AssemblyInfo.cs new file mode 100644 index 000000000..6a81089a0 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/AssemblyInfo.cs @@ -0,0 +1,17 @@ +// +// Copyright 2018, OpenCensus 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. +// + +[assembly: System.CLSCompliant(true)] diff --git a/src/OpenCensus.Collector.Dependencies/DependenciesCollector.cs b/src/OpenCensus.Collector.Dependencies/DependenciesCollector.cs new file mode 100644 index 000000000..427627d88 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/DependenciesCollector.cs @@ -0,0 +1,70 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using OpenCensus.Collector.Dependencies.Common; + using OpenCensus.Collector.Dependencies.Implementation; + using OpenCensus.Trace; + using OpenCensus.Trace.Propagation; + + /// + /// Dependencies collector. + /// + public class DependenciesCollector : IDisposable + { + private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration options for dependencies collector. + /// Tracer to record traced with. + /// Sampler to use to sample dependnecy calls. + /// Propagation component to use to encode span context to the wire. + public DependenciesCollector(DependenciesCollectorOptions options, ITracer tracer, ISampler sampler, IPropagationComponent propagationComponent) + { + this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber( + new Dictionary, ListenerHandler>>() + { { "HttpHandlerDiagnosticListener", (t, s) => new HttpHandlerDiagnosticListener(t, s, propagationComponent) } }, + tracer, + x => + { + ISampler s = null; + try + { + s = options.CustomSampler(x); + } + catch (Exception e) + { + s = null; + DependenciesCollectorEventSource.Log.ExceptionInCustomSampler(e); + } + + return s == null ? sampler : s; + }); + this.diagnosticSourceSubscriber.Subscribe(); + } + + public void Dispose() + { + this.diagnosticSourceSubscriber.Dispose(); + } + } +} diff --git a/src/OpenCensus.Collector.Dependencies/DependenciesCollectorEventSource.cs b/src/OpenCensus.Collector.Dependencies/DependenciesCollectorEventSource.cs new file mode 100644 index 000000000..66a4c9259 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/DependenciesCollectorEventSource.cs @@ -0,0 +1,74 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Tracing; + using System.Globalization; + using System.Text; + using System.Threading; + + /// + /// EventSource listing ETW events emitted from the project. + /// + [EventSource(Name = "OpenCensus.Collector.Dependencies")] + internal class DependenciesCollectorEventSource : EventSource + { + internal static DependenciesCollectorEventSource Log = new DependenciesCollectorEventSource(); + + [NonEvent] + public void ExceptionInCustomSampler(Exception ex) + { + if (Log.IsEnabled(EventLevel.Warning, EventKeywords.All)) + { + this.ExceptionInCustomSampler(ToInvariantString(ex)); + } + } + + [Event(1, Message = "Context is NULL in end callback. Span will not be recorded.", Level = EventLevel.Warning)] + public void NullContext() + { + this.WriteEvent(1); + } + + [Event(2, Message = "Error getting custom sampler, the default sampler will be used. Exception : {0}", Level = EventLevel.Warning)] + public void ExceptionInCustomSampler(string ex) + { + this.WriteEvent(2, ex); + } + + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + private static string ToInvariantString(Exception exception) + { + CultureInfo originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/src/OpenCensus.Collector.Dependencies/DependenciesCollectorOptions.cs b/src/OpenCensus.Collector.Dependencies/DependenciesCollectorOptions.cs new file mode 100644 index 000000000..3ca09d4f4 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/DependenciesCollectorOptions.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies +{ + using System; + using System.Net.Http; + using OpenCensus.Trace; + using OpenCensus.Trace.Sampler; + + /// + /// Options for dependencies collector. + /// + public class DependenciesCollectorOptions + { + private static Func defaultSampler = (req) => { return ((req.RequestUri != null) && req.RequestUri.ToString().Contains("zipkin.azurewebsites.net")) ? Samplers.NeverSample : null; }; + + /// + /// Initializes a new instance of the class. + /// + /// Custom sampling function, if any + public DependenciesCollectorOptions(Func sampler = null) + { + this.CustomSampler = sampler ?? defaultSampler; + } + + /// + /// Gets a hook to exclude calls based on domain + /// or other per-request criterion. + /// + public Func CustomSampler { get; private set; } + } +} diff --git a/src/OpenCensus.Collector.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenCensus.Collector.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs new file mode 100644 index 000000000..f190ec55d --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs @@ -0,0 +1,135 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Implementation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading.Tasks; + using OpenCensus.Collector.Dependencies.Common; + using OpenCensus.Trace; + using OpenCensus.Trace.Propagation; + using OpenCensus.Trace.Sampler; + + internal class HttpHandlerDiagnosticListener : ListenerHandler + { + private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request"); + private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response"); + private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception"); + private readonly PropertyFetcher stopRequestStatusFetcher = new PropertyFetcher("RequestTaskStatus"); + + private readonly IPropagationComponent propagationComponent; + + public HttpHandlerDiagnosticListener(ITracer tracer, Func samplerFactory, IPropagationComponent propagationComponent) + : base("HttpHandlerDiagnosticListener", tracer, samplerFactory) + { + this.propagationComponent = propagationComponent; + } + + public override void OnStartActivity(Activity activity, object payload) + { + if (!(this.startRequestFetcher.Fetch(payload) is HttpRequestMessage request)) + { + // Debug.WriteLine("request is null"); + return; + } + + this.Tracer.SpanBuilder(request.RequestUri.AbsolutePath, SpanKind.Client).SetSampler(this.SamplerFactory(request)).StartScopedSpan(out ISpan span); + span.PutHttpMethodAttribute(request.Method.ToString()); + span.PutHttpHostAttribute(request.RequestUri.Host, request.RequestUri.Port); + span.PutHttpPathAttribute(request.RequestUri.AbsolutePath); + request.Headers.TryGetValues("User-Agent", out IEnumerable userAgents); + span.PutHttpUserAgentAttribute(userAgents?.FirstOrDefault()); + span.PutHttpRawUrlAttribute(request.RequestUri.OriginalString); + + this.propagationComponent.TextFormat.Inject(span.Context, request, (r, k, v) => r.Headers.Add(k, v)); + } + + public override void OnStopActivity(Activity activity, object payload) + { + var span = this.Tracer.CurrentSpan; + + if (span == null) + { + DependenciesCollectorEventSource.Log.NullContext(); + return; + } + + var requestTaskStatus = this.stopRequestStatusFetcher.Fetch(payload) as TaskStatus?; + + if (requestTaskStatus.HasValue) + { + if (requestTaskStatus != TaskStatus.RanToCompletion) + { + span.Status = Status.Unknown; + + if (requestTaskStatus == TaskStatus.Canceled) + { + span.Status = Status.Cancelled; + } + } + } + + if (!(this.stopResponseFetcher.Fetch(payload) is HttpResponseMessage response)) + { + // response could be null for DNS issues, timeouts, etc... + // TODO: how do we make sure we will not close a scope that wasn't opened? + + span.End(); + return; + } + + span.PutHttpStatusCode((int)response.StatusCode, response.ReasonPhrase); + + span.End(); + } + + public override void OnException(Activity activity, object payload) + { + if (!(this.stopExceptionFetcher.Fetch(payload) is Exception exc)) + { + // Debug.WriteLine("response is null"); + return; + } + + var span = this.Tracer.CurrentSpan; + + if (span == null) + { + // TODO: Notify that span got lost + return; + } + + if (exc is HttpRequestException) + { + // TODO: on netstandard this will be System.Net.Http.WinHttpException: The server name or address could not be resolved + if (exc.InnerException is WebException && + ((WebException)exc.InnerException).Status == WebExceptionStatus.NameResolutionFailure) + { + span.Status = Status.InvalidArgument; + } + else if (exc.InnerException != null) + { + span.Status = Status.Unknown.WithDescription(exc.Message); + } + } + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceListener.cs b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceListener.cs new file mode 100644 index 000000000..dc7f66abe --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceListener.cs @@ -0,0 +1,83 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Common +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + internal class DiagnosticSourceListener : IObserver>, IDisposable + { + private readonly string sourceName; + private readonly ListenerHandler handler; + + public DiagnosticSourceListener(string sourceName, ListenerHandler handler) + { + this.sourceName = sourceName; + this.handler = handler; + } + + public IDisposable Subscription { get; set; } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + if (Activity.Current == null) + { + Debug.WriteLine("Activity is null " + value.Key); + return; + } + + try + { + if (value.Key.EndsWith("Start")) + { + this.handler.OnStartActivity(Activity.Current, value.Value); + } + else if (value.Key.EndsWith("Stop")) + { + this.handler.OnStopActivity(Activity.Current, value.Value); + } + else if (value.Key.EndsWith("Exception")) + { + this.handler.OnException(Activity.Current, value.Value); + } + else + { + this.handler.OnCustom(value.Key, Activity.Current, value.Value); + } + } + catch (Exception e) + { + // Debug.WriteLine(e); + // TODO: make sure to output the handler name as part of error message + } + } + + public void Dispose() + { + this.Subscription?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceSubscriber.cs b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceSubscriber.cs new file mode 100644 index 000000000..8edbc9472 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/DiagnosticSourceSubscriber.cs @@ -0,0 +1,96 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Common +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Diagnostics; + using System.Net.Http; + using System.Threading; + using OpenCensus.Trace; + + internal class DiagnosticSourceSubscriber : IDisposable, IObserver + { + private readonly Dictionary, ListenerHandler>> handlers; + private readonly ITracer tracer; + private readonly Func sampler; + private ConcurrentDictionary subscriptions; + private bool disposing; + private IDisposable subscription; + + public DiagnosticSourceSubscriber(Dictionary, ListenerHandler>> handlers, ITracer tracer, Func sampler) + { + this.subscriptions = new ConcurrentDictionary(); + this.handlers = handlers; + this.tracer = tracer; + this.sampler = sampler; + } + + public void Subscribe() + { + if (this.subscription == null) + { + this.subscription = DiagnosticListener.AllListeners.Subscribe(this); + } + } + + public void OnNext(DiagnosticListener value) + { + if (!Volatile.Read(ref this.disposing) && this.subscriptions != null) + { + if (this.handlers.ContainsKey(value.Name)) + { + this.subscriptions.GetOrAdd(value.Name, name => + { + var dl = new DiagnosticSourceListener(value.Name, this.handlers[value.Name](this.tracer, this.sampler)); + dl.Subscription = value.Subscribe(dl); + return dl; + }); + } + } + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void Dispose() + { + Volatile.Write(ref this.disposing, true); + + var subsCopy = this.subscriptions; + this.subscriptions = null; + + var keys = subsCopy.Keys; + foreach (var key in keys) + { + if (subsCopy.TryRemove(key, out var sub)) + { + sub?.Dispose(); + } + } + + this.subscription?.Dispose(); + this.subscription = null; + } + } +} diff --git a/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/ListenerHandler.cs b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/ListenerHandler.cs new file mode 100644 index 000000000..2239cdb89 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/ListenerHandler.cs @@ -0,0 +1,69 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Common +{ + using System; + using System.Diagnostics; + using System.Net.Http; + using OpenCensus.Trace; + + internal abstract class ListenerHandler + { + protected readonly ITracer Tracer; + + protected readonly Func SamplerFactory; + + public ListenerHandler(string sourceName, ITracer tracer, Func samplerFactory) + { + this.SourceName = sourceName; + this.Tracer = tracer; + this.SamplerFactory = samplerFactory; + } + + public string SourceName { get; } + + public abstract void OnStartActivity(Activity activity, object payload); + + public virtual void OnStopActivity(Activity activity, object payload) + { + var span = this.Tracer.CurrentSpan; + + if (span == null) + { + // TODO: Notify that span got lost + return; + } + + foreach (var tag in activity.Tags) + { + span.PutAttribute(tag.Key, AttributeValue.StringAttributeValue(tag.Value)); + } + } + + public virtual void OnException(Activity activity, object payload) + { + var span = this.Tracer.CurrentSpan; + + // TODO: gather exception information + } + + public virtual void OnCustom(string name, Activity activity, object payload) + { + // if custom handler needs to react on other events - this method should be overridden + } + } +} diff --git a/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/PropertyFetcher.cs b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/PropertyFetcher.cs new file mode 100644 index 000000000..6a2bfb5d0 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/Implementation/OpenCensus.Collector.Dependencies.Common/PropertyFetcher.cs @@ -0,0 +1,94 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Common +{ + using System; + using System.Reflection; + + internal class PropertyFetcher + { + private readonly string propertyName; + private PropertyFetch innerFetcher; + + public PropertyFetcher(string propertyName) + { + this.propertyName = propertyName; + } + + public object Fetch(object obj) + { + if (this.innerFetcher == null) + { + var type = obj.GetType().GetTypeInfo(); + var property = type.GetDeclaredProperty(this.propertyName); + if (property == null) + { + property = type.GetProperty(this.propertyName); + } + + this.innerFetcher = PropertyFetch.FetcherForProperty(property); + } + + return this.innerFetcher?.Fetch(obj); + } + + // see https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs + private class PropertyFetch + { + /// + /// Create a property fetcher from a .NET Reflection PropertyInfo class that + /// represents a property of a particular type. + /// + public static PropertyFetch FetcherForProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + // returns null on any fetch. + return new PropertyFetch(); + } + + var typedPropertyFetcher = typeof(TypedFetchProperty<,>); + var instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( + propertyInfo.DeclaringType, propertyInfo.PropertyType); + return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, propertyInfo); + } + + /// + /// Given an object, fetch the property that this propertyFetch represents. + /// + public virtual object Fetch(object obj) + { + return null; + } + + private class TypedFetchProperty : PropertyFetch + { + private readonly Func propertyFetch; + + public TypedFetchProperty(PropertyInfo property) + { + this.propertyFetch = (Func)property.GetMethod.CreateDelegate(typeof(Func)); + } + + public override object Fetch(object obj) + { + return this.propertyFetch((TObject)obj); + } + } + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Collector.Dependencies/OpenCensus.Collector.Dependencies.csproj b/src/OpenCensus.Collector.Dependencies/OpenCensus.Collector.Dependencies.csproj new file mode 100644 index 000000000..39d1c3691 --- /dev/null +++ b/src/OpenCensus.Collector.Dependencies/OpenCensus.Collector.Dependencies.csproj @@ -0,0 +1,35 @@ + + + + + netstandard2.0 + netstandard2.0 + True + + + + OpenCensus collector for calls to dependencies + Tracing;OpenCensus;Management;Monitoring;distributed-tracing;HttpClient + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + + + + + + + All + + + + + + + + diff --git a/src/OpenCensus.Collector.StackExchangeRedis/AssemblyInfo.cs b/src/OpenCensus.Collector.StackExchangeRedis/AssemblyInfo.cs new file mode 100644 index 000000000..67cfe3e4f --- /dev/null +++ b/src/OpenCensus.Collector.StackExchangeRedis/AssemblyInfo.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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.Runtime.CompilerServices; + +[assembly:CLSCompliant(true)] + +[assembly: InternalsVisibleTo("OpenCensus.Collector.StackExchangeRedis.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif \ No newline at end of file diff --git a/src/OpenCensus.Collector.StackExchangeRedis/Implementation/RedisProfilerEntryToSpanConverter.cs b/src/OpenCensus.Collector.StackExchangeRedis/Implementation/RedisProfilerEntryToSpanConverter.cs new file mode 100644 index 000000000..dbc22f4a1 --- /dev/null +++ b/src/OpenCensus.Collector.StackExchangeRedis/Implementation/RedisProfilerEntryToSpanConverter.cs @@ -0,0 +1,154 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.StackExchangeRedis.Implementation +{ + using System; + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + using StackExchange.Redis.Profiling; + + internal static class RedisProfilerEntryToSpanConverter + { + public static void DrainSession(ISpan parentSpan, IEnumerable sessionCommands, ISampler sampler, ICollection spans) + { + var parentContext = parentSpan?.Context ?? SpanContext.Invalid; + + foreach (var command in sessionCommands) + { + string name = command.Command; // Example: SET; + if (string.IsNullOrEmpty(name)) + { + name = "name"; + } + + if (ShouldSample(parentContext, name, sampler, out var context, out var parentSpanId)) + { + var sd = ProfiledCommandToSpanData(context, name, parentSpanId, command); + spans.Add(sd); + } + } + } + + internal static bool ShouldSample(ISpanContext parentContext, string name, ISampler sampler, out ISpanContext context, out ISpanId parentSpanId) + { + var traceId = TraceId.Invalid; + var tracestate = Tracestate.Empty; + parentSpanId = SpanId.Invalid; + var parentOptions = TraceOptions.Default; + + if (parentContext.IsValid) + { + traceId = parentContext.TraceId; + parentSpanId = parentContext.SpanId; + parentOptions = parentContext.TraceOptions; + } + else + { + traceId = TraceId.FromBytes(Guid.NewGuid().ToByteArray()); + } + + var result = parentOptions.IsSampled; + bool hasRemoteParent = false; + var spanId = SpanId.FromBytes(Guid.NewGuid().ToByteArray(), 8); + var traceOptions = TraceOptions.Default; + + if (sampler != null) + { + var builder = TraceOptions.Builder(parentContext.TraceOptions); + result = sampler.ShouldSample(parentContext, hasRemoteParent, traceId, spanId, name, null); + builder = builder.SetIsSampled(result); + traceOptions = builder.Build(); + } + + context = SpanContext.Create(traceId, spanId, traceOptions, parentContext.Tracestate); + + return result; + } + + internal static ISpanData ProfiledCommandToSpanData(ISpanContext context, string name, ISpanId parentSpanId, IProfiledCommand command) + { + var hasRemoteParent = false; + + // use https://github.com/opentracing/specification/blob/master/semantic_conventions.md for now + + // Timing example: + // command.CommandCreated; //2019-01-10 22:18:28Z + + // command.CreationToEnqueued; // 00:00:32.4571995 + // command.EnqueuedToSending; // 00:00:00.0352838 + // command.SentToResponse; // 00:00:00.0060586 + // command.ResponseToCompletion; // 00:00:00.0002601 + + // Total: + // command.ElapsedTime; // 00:00:32.4988020 + + // TODO: make timestamp with the better precision + Timestamp startTimestamp = Timestamp.FromMillis(new DateTimeOffset(command.CommandCreated).ToUnixTimeMilliseconds()); + + var timestamp = new DateTimeOffset(command.CommandCreated).Add(command.CreationToEnqueued); + var annotations = TimedEvents.Create( + new List>() + { + TimedEvent.Create(Timestamp.FromMillis(timestamp.ToUnixTimeMilliseconds()), Annotation.FromDescription("Enqueued")), + TimedEvent.Create(Timestamp.FromMillis((timestamp = timestamp.Add(command.EnqueuedToSending)).ToUnixTimeMilliseconds()), Annotation.FromDescription("Sent")), + TimedEvent.Create(Timestamp.FromMillis((timestamp = timestamp.Add(command.SentToResponse)).ToUnixTimeMilliseconds()), Annotation.FromDescription("ResponseRecieved")), + }, + droppedEventsCount: 0); + + Timestamp endTimestamp = Timestamp.FromMillis(new DateTimeOffset(command.CommandCreated.Add(command.ElapsedTime)).ToUnixTimeMilliseconds()); + + // TODO: deal with the re-transmission + // command.RetransmissionOf; + // command.RetransmissionReason; + + var attributesMap = new Dictionary() + { + // TODO: pre-allocate constant attribute and reuse + { "db.type", AttributeValue.StringAttributeValue("redis") }, + + // Example: "redis.flags": None, DemandMaster + { "redis.flags", AttributeValue.StringAttributeValue(command.Flags.ToString()) }, + }; + + if (command.Command != null) + { + // Example: "db.statement": SET; + attributesMap.Add("db.statement", AttributeValue.StringAttributeValue(command.Command)); + } + + if (command.EndPoint != null) + { + // Example: "db.instance": Unspecified/localhost:6379[0] + attributesMap.Add("db.instance", AttributeValue.StringAttributeValue(command.EndPoint.ToString() + "[" + command.Db + "]")); + } + + var attributes = Attributes.Create(attributesMap, 0); + + ITimedEvents messageOrNetworkEvents = null; + ILinks links = null; + int? childSpanCount = 0; + + // TODO: this is strange that IProfiledCommand doesn't give the result + Status status = Status.Ok; + SpanKind kind = SpanKind.Client; + + return SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + } + } +} diff --git a/src/OpenCensus.Collector.StackExchangeRedis/OpenCensus.Collector.StackExchangeRedis.csproj b/src/OpenCensus.Collector.StackExchangeRedis/OpenCensus.Collector.StackExchangeRedis.csproj new file mode 100644 index 000000000..c0175009b --- /dev/null +++ b/src/OpenCensus.Collector.StackExchangeRedis/OpenCensus.Collector.StackExchangeRedis.csproj @@ -0,0 +1,25 @@ + + + + + netstandard2.0;net461 + netstandard2.0 + True + + + + OpenCensus collector for Redis calls made by StackExchange.Redis library. + Tracing;OpenCensus;Management;Monitoring;distributed-tracing;Redis;StackExchange.Redis + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + + + + + diff --git a/src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollector.cs b/src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollector.cs new file mode 100644 index 000000000..3f649a12d --- /dev/null +++ b/src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollector.cs @@ -0,0 +1,133 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.StackExchangeRedis +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using OpenCensus.Collector.StackExchangeRedis.Implementation; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + using StackExchange.Redis.Profiling; + + /// + /// Redis calls collector. + /// + public class StackExchangeRedisCallsCollector : IDisposable + { + private readonly ITracer tracer; + private readonly IExportComponent exporter; + private readonly ISampler sampler; + + private readonly CancellationTokenSource cancellationTokenSource; + private readonly CancellationToken cancellationToken; + + private readonly ProfilingSession defaultSession = new ProfilingSession(); + private readonly ConcurrentDictionary cache = new ConcurrentDictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// Configuration options for dependencies collector. + /// Tracer to record traced with. + /// Sampler to use to sample dependnecy calls. + /// TEMPORARY: handler to send data to. + public StackExchangeRedisCallsCollector(StackExchangeRedisCallsCollectorOptions options, ITracer tracer, ISampler sampler, IExportComponent exportComponent) + { + this.tracer = tracer; + this.exporter = exportComponent; + this.sampler = sampler; + + this.cancellationTokenSource = new CancellationTokenSource(); + this.cancellationToken = this.cancellationTokenSource.Token; + Task.Factory.StartNew(this.DumpEntries, TaskCreationOptions.LongRunning, this.cancellationToken); + } + + /// + /// Returns session for the Redis calls recording. + /// + /// Session associated with the current span context to record Redis calls. + public Func GetProfilerSessionsFactory() + { + // This implementation shares session for multiple Redis calls made inside a single parent Span. + // It cost an additional lookup in concurrent dictionary, but potentially saves an allocation + // if many calls to Redis were made from the same parent span. + // Creating a session per Redis call may be more optimal solution here as sampling will not + // require any locking and can redis the number of buffered sessions significantly. + return () => + { + var span = this.tracer.CurrentSpan; + + // when there are no spans in current context - BlankSpan will be returned + // BlankSpan has invalid context. It's OK to use a single profiler session + // for all invalid context's spans. + // + // It would be great to allow to check sampling here, but it is impossible + // with the current model to start a new trace id here - no way to pass it + // to the resulting Span. + if (span == null || !span.Context.IsValid) + { + return this.defaultSession; + } + + // TODO: As a performance optimization the check for sampling may be implemented here + // The problem with this approach would be that SpanId cannot be generated here + // So if sampler uses SpanId in algorithm - results would be inconsistent + var session = this.cache.GetOrAdd(span, (s) => new ProfilingSession(s)); + return session; + }; + } + + /// + public void Dispose() + { + this.cancellationTokenSource.Cancel(); + this.cancellationTokenSource.Dispose(); + } + + private void DumpEntries(object state) + { + while (!this.cancellationToken.IsCancellationRequested) + { + var spans = new List(); + + RedisProfilerEntryToSpanConverter.DrainSession(null, this.defaultSession.FinishProfiling(), this.sampler, spans); + + foreach (var entry in this.cache) + { + var span = entry.Key; + if (span.HasEnded) + { + this.cache.TryRemove(span, out var session); + RedisProfilerEntryToSpanConverter.DrainSession(span, session.FinishProfiling(), this.sampler, spans); + } + else + { + this.cache.TryGetValue(span, out var session); + RedisProfilerEntryToSpanConverter.DrainSession(span, session.FinishProfiling(), this.sampler, spans); + } + } + + this.exporter.SpanExporter.ExportAsync(spans, CancellationToken.None).Wait(); + + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + } + } +} diff --git a/src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollectorOptions.cs b/src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollectorOptions.cs new file mode 100644 index 000000000..e35c75df1 --- /dev/null +++ b/src/OpenCensus.Collector.StackExchangeRedis/StackExchangeRedisCallsCollectorOptions.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.StackExchangeRedis +{ + /// + /// Options for dependencies collector. + /// + public class StackExchangeRedisCallsCollectorOptions + { + } +} diff --git a/src/OpenCensus.Exporter.ApplicationInsights/ApplicationInsightsExporter.cs b/src/OpenCensus.Exporter.ApplicationInsights/ApplicationInsightsExporter.cs new file mode 100644 index 000000000..93dd0818b --- /dev/null +++ b/src/OpenCensus.Exporter.ApplicationInsights/ApplicationInsightsExporter.cs @@ -0,0 +1,108 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.ApplicationInsights +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.ApplicationInsights.Extensibility; + using OpenCensus.Exporter.ApplicationInsights.Implementation; + using OpenCensus.Stats; + using OpenCensus.Trace.Export; + + /// + /// Exporter of Open Census traces and metrics to Azure Application Insights. + /// + public class ApplicationInsightsExporter + { + private const string TraceExporterName = "ApplicationInsightsTraceExporter"; + + private readonly TelemetryConfiguration telemetryConfiguration; + + private readonly IViewManager viewManager; + + private readonly IExportComponent exportComponent; + + private readonly object lck = new object(); + + private TraceExporterHandler handler; + + private CancellationTokenSource tokenSource; + + private Task workerThread; + + /// + /// Initializes a new instance of the class. + /// This exporter allows to send Open Census data to Azure Application Insights. + /// + /// Exporter to get traces and metrics from. + /// View manager to get stats from. + /// Telemetry configuration to use to report telemetry. + public ApplicationInsightsExporter(IExportComponent exportComponent, IViewManager viewManager, TelemetryConfiguration telemetryConfiguration) + { + this.exportComponent = exportComponent; + this.viewManager = viewManager; + this.telemetryConfiguration = telemetryConfiguration; + } + + /// + /// Start exporter. + /// + public void Start() + { + lock (this.lck) + { + if (this.handler != null) + { + return; + } + + this.handler = new TraceExporterHandler(this.telemetryConfiguration); + + this.exportComponent.SpanExporter.RegisterHandler(TraceExporterName, this.handler); + + this.tokenSource = new CancellationTokenSource(); + + CancellationToken token = this.tokenSource.Token; + + var metricsExporter = new MetricsExporterThread(this.telemetryConfiguration, this.viewManager, token, TimeSpan.FromMinutes(1)); + this.workerThread = Task.Factory.StartNew((Action)metricsExporter.WorkerThread, TaskCreationOptions.LongRunning); + } + } + + /// + /// Stop exporter. + /// + public void Stop() + { + lock (this.lck) + { + if (this.handler == null) + { + return; + } + + this.exportComponent.SpanExporter.UnregisterHandler(TraceExporterName); + this.tokenSource.Cancel(); + this.workerThread.Wait(); + this.tokenSource = null; + + this.handler = null; + } + } + } +} diff --git a/src/OpenCensus.Exporter.ApplicationInsights/Implementation/MetricsExporterThread.cs b/src/OpenCensus.Exporter.ApplicationInsights/Implementation/MetricsExporterThread.cs new file mode 100644 index 000000000..90c3f3ca7 --- /dev/null +++ b/src/OpenCensus.Exporter.ApplicationInsights/Implementation/MetricsExporterThread.cs @@ -0,0 +1,195 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.ApplicationInsights.Implementation +{ + using System; + using System.Diagnostics; + using System.Threading; + using Microsoft.ApplicationInsights; + using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.Extensibility; + using OpenCensus.Stats; + using OpenCensus.Stats.Aggregations; + + internal class MetricsExporterThread + { + private readonly IViewManager viewManager; + + private readonly TelemetryClient telemetryClient; + + private readonly TimeSpan collectionInterval; + + private readonly TimeSpan cancellationInterval = TimeSpan.FromMilliseconds(10); + + private readonly CancellationToken token; + + public MetricsExporterThread(TelemetryConfiguration telemetryConfiguration, IViewManager viewManager, CancellationToken token, TimeSpan collectionInterval) + { + this.telemetryClient = new TelemetryClient(telemetryConfiguration); + this.viewManager = viewManager; + this.collectionInterval = collectionInterval; + this.token = token; + } + + public void WorkerThread() + { + try + { + Stopwatch sw = new Stopwatch(); + + while (!this.token.IsCancellationRequested) + { + sw.Start(); + this.Export(); + sw.Stop(); + + // adjust interval for data collection time + var sleepInterval = this.collectionInterval.Subtract(sw.Elapsed); + + // allow faster thread cancellation + while (sleepInterval > this.cancellationInterval && !this.token.IsCancellationRequested) + { + Thread.Sleep(this.cancellationInterval); + sleepInterval = sleepInterval.Subtract(this.cancellationInterval); + } + + Thread.Sleep(sleepInterval); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + + internal void Export() + { + foreach (var view in this.viewManager.AllExportedViews) + { + var data = this.viewManager.GetView(view.Name); + + foreach (var value in data.AggregationMap) + { + var metricTelemetry = new MetricTelemetry + { + Name = data.View.Name.AsString, + }; + + for (int i = 0; i < value.Key.Values.Count; i++) + { + var name = data.View.Columns[i].Name; + var val = value.Key.Values[i].AsString; + metricTelemetry.Properties.Add(name, val); + } + + // Now those properties needs to be populated. + // + // metricTelemetry.Sum + // metricTelemetry.Count + // metricTelemetry.Max + // metricTelemetry.Min + // metricTelemetry.StandardDeviation + // + // See data model for clarification on the meaning of those fields. + // https://docs.microsoft.com/azure/application-insights/application-insights-data-model-metric-telemetry + + value.Value.Match( + (combined) => + { + if (combined is ISumDataDouble sum) + { + metricTelemetry.Sum = sum.Sum; + } + + return null; + }, + (combined) => + { + if (combined is ISumDataLong sum) + { + metricTelemetry.Sum = sum.Sum; + } + + return null; + }, + (combined) => + { + if (combined is ICountData count) + { + metricTelemetry.Sum = count.Count; + } + + return null; + }, + (combined) => + { + if (combined is IMeanData mean) + { + metricTelemetry.Sum = mean.Mean * mean.Count; + metricTelemetry.Count = (int)mean.Count; + metricTelemetry.Max = mean.Max; + metricTelemetry.Min = mean.Min; + } + + return null; + }, + (combined) => + { + if (combined is IDistributionData dist) + { + metricTelemetry.Sum = dist.Mean * dist.Count; + metricTelemetry.Count = (int)dist.Count; + metricTelemetry.Min = dist.Min; + metricTelemetry.Max = dist.Max; + metricTelemetry.StandardDeviation = dist.SumOfSquaredDeviations; + } + + return null; + }, + (combined) => + { + if (combined is ILastValueDataDouble lastValue) + { + metricTelemetry.Sum = lastValue.LastValue; + } + + return null; + }, + (combined) => + { + if (combined is ILastValueDataLong lastValue) + { + metricTelemetry.Sum = lastValue.LastValue; + } + + return null; + }, + (combined) => + { + if (combined is IAggregationData aggregationData) + { + // TODO: report an error + } + + return null; + }); + this.telemetryClient.TrackMetric(metricTelemetry); + } + } + } + } +} diff --git a/src/OpenCensus.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs b/src/OpenCensus.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs new file mode 100644 index 000000000..f5220b56e --- /dev/null +++ b/src/OpenCensus.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs @@ -0,0 +1,515 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.ApplicationInsights.Implementation +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Threading.Tasks; + using Microsoft.ApplicationInsights; + using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.Extensibility.Implementation; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + + internal class TraceExporterHandler : IHandler + { + private readonly TelemetryClient telemetryClient; + + public TraceExporterHandler(TelemetryConfiguration telemetryConfiguration) + { + this.telemetryClient = new TelemetryClient(telemetryConfiguration); + } + + public async Task ExportAsync(IEnumerable spanDataList) + { + await Task.Run(async () => + { + foreach (var span in spanDataList) + { + this.ExtractGenericProperties( + span, + out var resultKind, + out var timestamp, + out var name, + out var resultCode, + out var props, + out var traceId, + out var spanId, + out var parentId, + out var tracestate, + out var success, + out var duration); + + string data = null; + string target = null; + string type = null; + string userAgent = null; + + IAttributeValue spanKindAttr = null; + IAttributeValue errorAttr = null; + IAttributeValue httpStatusCodeAttr = null; + IAttributeValue httpMethodAttr = null; + IAttributeValue httpPathAttr = null; + IAttributeValue httpHostAttr = null; + IAttributeValue httpUrlAttr = null; + IAttributeValue httpUserAgentAttr = null; + IAttributeValue httpRouteAttr = null; + IAttributeValue httpPortAttr = null; + + foreach (var attr in span.Attributes.AttributeMap) + { + var key = attr.Key; + + switch (attr.Key) + { + case "span.kind": + spanKindAttr = attr.Value; + break; + case "error": + errorAttr = attr.Value; + break; + case "http.method": + httpMethodAttr = attr.Value; + break; + case "http.path": + httpPathAttr = attr.Value; + break; + case "http.host": + httpHostAttr = attr.Value; + break; + case "http.url": + httpUrlAttr = attr.Value; + break; + case "http.status_code": + httpStatusCodeAttr = attr.Value; + break; + case "http.user_agent": + httpUserAgentAttr = attr.Value; + break; + case "http.route": + httpRouteAttr = attr.Value; + break; + case "http.port": + httpPortAttr = attr.Value; + break; + default: + var value = attr.Value.Match( + (s) => { return s; }, + (b) => { return b.ToString(); }, + (l) => { return l.ToString(); }, + (d) => { return d.ToString(); }, + (obj) => { return obj.ToString(); }); + + AddPropertyWithAdjustedName(props, attr.Key, value); + + break; + } + } + + var linkId = 0; + foreach (var link in span.Links.Links) + { + AddPropertyWithAdjustedName(props, "link" + linkId + "_traceId", link.TraceId.ToLowerBase16()); + AddPropertyWithAdjustedName(props, "link" + linkId + "_spanId", link.SpanId.ToLowerBase16()); + AddPropertyWithAdjustedName(props, "link" + linkId + "_type", link.Type.ToString()); + + foreach (var attr in link.Attributes) + { + AddPropertyWithAdjustedName(props, "link" + linkId + "_" + attr.Key, attr.Value.Match((s) => s, (b) => b.ToString(), (l) => l.ToString(), (d) => d.ToString(), (obj) => obj.ToString())); + } + + ++linkId; + } + + foreach (var t in span.Annotations.Events) + { + var log = new TraceTelemetry(t.Event.Description); + + if (t.Timestamp != null) + { + var logTimestamp = DateTimeOffset.FromUnixTimeSeconds(t.Timestamp.Seconds); + logTimestamp = logTimestamp.Add(TimeSpan.FromTicks(t.Timestamp.Nanos / 100)); + log.Timestamp = logTimestamp; + } + + foreach (var attr in t.Event.Attributes) + { + var value = attr.Value.Match( + (s) => { return s; }, + (b) => { return b.ToString(); }, + (l) => { return l.ToString(); }, + (d) => { return d.ToString(); }, + (obj) => { return obj.ToString(); }); + + AddPropertyWithAdjustedName(log.Properties, attr.Key, value); + } + + log.Context.Operation.Id = traceId; + log.Context.Operation.ParentId = string.Concat("|", traceId, ".", spanId, "."); + + this.telemetryClient.Track(log); + } + + foreach (var m in span.MessageEvents.Events) + { + var log = new TraceTelemetry(); + + if (m.Timestamp != null) + { + var logTimestamp = DateTimeOffset.FromUnixTimeSeconds(m.Timestamp.Seconds); + logTimestamp = logTimestamp.Add(TimeSpan.FromTicks(m.Timestamp.Nanos / 100)); + log.Timestamp = logTimestamp; + } + + log.Message = string.Concat( + "MessageEvent. messageId: '", + m.Event.MessageId, + "', type: '", + m.Event.Type.ToString(), + "', compressed size: '", + m.Event.CompressedMessageSize, + "', uncompressed size: '", + m.Event.UncompressedMessageSize, + "'"); + + log.Context.Operation.Id = traceId; + log.Context.Operation.ParentId = string.Concat("|", traceId, ".", spanId, "."); + + this.telemetryClient.Track(log); + } + + this.OverwriteSpanKindFromAttribute(spanKindAttr, ref resultKind); + this.OverwriteErrorAttribute(errorAttr, ref success); + this.OverwriteFieldsForHttpSpans( + httpMethodAttr, + httpUrlAttr, + httpHostAttr, + httpPathAttr, + httpStatusCodeAttr, + httpUserAgentAttr, + httpRouteAttr, + httpPortAttr, + ref name, + ref resultCode, + ref data, + ref target, + ref type, + ref userAgent); + + // BUILDING resulting telemetry + OperationTelemetry result; + if (resultKind == SpanKind.Client) + { + var resultD = new DependencyTelemetry(); + resultD.ResultCode = resultCode; + resultD.Data = data; + resultD.Target = target; + resultD.Type = type; + + result = resultD; + } + else + { + var resultR = new RequestTelemetry(); + resultR.ResponseCode = resultCode; + Uri.TryCreate(data, UriKind.RelativeOrAbsolute, out var url); + resultR.Url = url; + result = resultR; + } + + result.Success = success; + + result.Timestamp = timestamp; + result.Name = name; + result.Context.Operation.Id = traceId; + result.Context.User.UserAgent = userAgent; + + foreach (var prop in props) + { + AddPropertyWithAdjustedName(result.Properties, prop.Key, prop.Value); + } + + if (parentId != null) + { + result.Context.Operation.ParentId = string.Concat("|", traceId, ".", parentId, "."); + } + + // TODO: I don't understant why this concatanation is required + result.Id = string.Concat("|", traceId, ".", spanId, "."); + + foreach (var ts in tracestate.Entries) + { + result.Properties[ts.Key] = ts.Value; + } + + result.Duration = duration; + + // TODO: deal with those: + // span.ChildSpanCount + // span.Context.IsValid; + // span.Context.TraceOptions; + + this.telemetryClient.Track(result); + } + }); + } + + private static void AddPropertyWithAdjustedName(IDictionary props, string name, string value) + { + var n = name; + var i = 0; + while (props.ContainsKey(n)) + { + n = name + "_" + i; + ++i; + } + + props.Add(n, value); + } + + private void ExtractGenericProperties(ISpanData span, out SpanKind resultKind, out DateTimeOffset timestamp, out string name, out string resultCode, out IDictionary props, out string traceId, out string spanId, out string parentId, out Tracestate tracestate, out bool? success, out TimeSpan duration) + { + resultKind = span.Kind; + + // TODO: Should this be a part of generic logic? + if (resultKind == SpanKind.Unspecified) + { + if (span.HasRemoteParent.HasValue && span.HasRemoteParent.Value) + { + resultKind = SpanKind.Server; + } + else + { + resultKind = SpanKind.Client; + } + } + + // 1 tick is 100 ns + timestamp = DateTimeOffset.FromUnixTimeSeconds(span.StartTimestamp.Seconds); + timestamp = timestamp.Add(TimeSpan.FromTicks(span.StartTimestamp.Nanos / 100)); + + name = span.Name; + + props = new Dictionary(); + + traceId = span.Context.TraceId.ToLowerBase16(); + spanId = span.Context.SpanId.ToLowerBase16(); + parentId = null; + if (span.ParentSpanId != null && span.ParentSpanId.IsValid) + { + parentId = span.ParentSpanId.ToLowerBase16(); + } + + resultCode = null; + success = null; + if (span.Status != null) + { + resultCode = ((int)span.Status.CanonicalCode).ToString(); + success = span.Status.IsOk; + if (!string.IsNullOrEmpty(span.Status.Description)) + { + props["statusDescription"] = span.Status.Description; + } + } + + tracestate = span.Context.Tracestate; + + var durationTs = span.EndTimestamp.SubtractTimestamp(span.StartTimestamp); + duration = TimeSpan.FromTicks((durationTs.Seconds * TimeSpan.TicksPerSecond) + (durationTs.Nanos / 100)); + } + + private void OverwriteSpanKindFromAttribute(IAttributeValue spanKindAttr, ref SpanKind resultKind) + { + // override span kind with attribute named span.kind + if (spanKindAttr != null) + { + var kind = spanKindAttr.Match((s) => s, null, null, null, null); + + if (kind == "server") + { + resultKind = SpanKind.Server; + } + else + { + resultKind = SpanKind.Client; + } + } + } + + private void OverwriteErrorAttribute(IAttributeValue errorAttr, ref bool? success) + { + if (errorAttr != null) + { + success = errorAttr.Match((s) => !(s == "true"), (b) => !b, null, null, null); + } + } + + private void OverwriteFieldsForHttpSpans( + IAttributeValue httpMethodAttr, + IAttributeValue httpUrlAttr, + IAttributeValue httpHostAttr, + IAttributeValue httpPathAttr, + IAttributeValue httpStatusCodeAttr, + IAttributeValue httpUserAgentAttr, + IAttributeValue httpRouteAttr, + IAttributeValue httpPortAttr, + ref string name, + ref string resultCode, + ref string data, + ref string target, + ref string type, + ref string userAgent) + { + if (httpStatusCodeAttr != null) + { + resultCode = httpStatusCodeAttr.Match((s) => s, null, (l) => l.ToString(CultureInfo.InvariantCulture), null, null); + type = "Http"; + } + + Uri url = null; + + if (httpUrlAttr != null) + { + var urlString = httpUrlAttr.Match((s) => s, null, null, null, null); + Uri.TryCreate(urlString, UriKind.RelativeOrAbsolute, out url); + } + + string httpMethod = null; + string httpPath = null; + string httpHost = null; + string httpRoute = null; + string httpPort = null; + + if (httpMethodAttr != null) + { + httpMethod = httpMethodAttr.Match((s) => s, null, null, null, null); + type = "Http"; + } + + if (httpPathAttr != null) + { + httpPath = httpPathAttr.Match((s) => s, null, null, null, null); + type = "Http"; + } + + if (httpHostAttr != null) + { + httpHost = httpHostAttr.Match((s) => s, null, null, null, null); + type = "Http"; + } + + if (httpUserAgentAttr != null) + { + userAgent = httpUserAgentAttr.Match((s) => s, null, null, null, null); + type = "Http"; + } + + if (httpRouteAttr != null) + { + httpRoute = httpRouteAttr.Match((s) => s, null, null, null, null); + type = "Http"; + } + + if (httpRouteAttr != null) + { + httpRoute = httpRouteAttr.Match((s) => s, null, null, null, null); + type = "Http"; + } + + if (httpPortAttr != null) + { + httpPort = httpPortAttr.Match((s) => s, null, (l) => l.ToString(), null, null); + type = "Http"; + } + + // restore optional fields when possible + if ((httpPathAttr == null) && (url != null)) + { + if (url.IsAbsoluteUri) + { + httpPath = url.LocalPath; + } + else + { + int idx = url.OriginalString.IndexOf('?'); + if (idx != -1) + { + httpPath = url.OriginalString.Substring(0, idx); + } + else + { + httpPath = url.OriginalString; + } + } + } + + if (url == null) + { + string urlString = string.Empty; + if (!string.IsNullOrEmpty(httpHost)) + { + urlString += "https://" + httpHost; + + if (!string.IsNullOrEmpty(httpPort)) + { + urlString += ":" + httpPort; + } + } + + if (!string.IsNullOrEmpty(httpPath)) + { + if (httpPath[0] != '/') + { + urlString += '/'; + } + + urlString += httpPath; + } + + if (!string.IsNullOrEmpty(urlString)) + { + Uri.TryCreate(urlString, UriKind.RelativeOrAbsolute, out url); + } + } + + // overwriting + if (httpPath != null || httpMethod != null || httpRoute != null) + { + if (httpRoute != null) + { + name = (httpMethod + " " + httpRoute).Trim(); + } + else + { + name = (httpMethod + " " + httpPath).Trim(); + } + } + + if (url != null) + { + data = url.ToString(); + } + + if ((url != null) && url.IsAbsoluteUri) + { + target = url.Host; + } + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Exporter.ApplicationInsights/OpenCensus.Exporter.ApplicationInsights.csproj b/src/OpenCensus.Exporter.ApplicationInsights/OpenCensus.Exporter.ApplicationInsights.csproj new file mode 100644 index 000000000..0fa1bbc38 --- /dev/null +++ b/src/OpenCensus.Exporter.ApplicationInsights/OpenCensus.Exporter.ApplicationInsights.csproj @@ -0,0 +1,31 @@ + + + + + net46;netstandard2.0 + netstandard2.0 + True + + + + Application Insights exporter for Open Census. + Tracing;OpenCensus;Management;Monitoring;application-insights;azure;distributed-tracing + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + + + + + + + All + + + + diff --git a/src/OpenCensus.Exporter.ApplicationInsights/Properties/AssemblyInfo.cs b/src/OpenCensus.Exporter.ApplicationInsights/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..70e45658d --- /dev/null +++ b/src/OpenCensus.Exporter.ApplicationInsights/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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.Runtime.CompilerServices; + +[assembly: System.CLSCompliant(true)] + +[assembly: InternalsVisibleTo("OpenCensus.Exporter.ApplicationInsights.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif \ No newline at end of file diff --git a/src/OpenCensus.Exporter.ApplicationInsights/README.md b/src/OpenCensus.Exporter.ApplicationInsights/README.md new file mode 100644 index 000000000..7c4a0f4bb --- /dev/null +++ b/src/OpenCensus.Exporter.ApplicationInsights/README.md @@ -0,0 +1,27 @@ +# Application Insights Exporter + +## Notes on questionable decisions + +1. Why `Span.Kind` has option to be `Unspecified`? Is it only needed for back + compatibility or it is valid long term? +2. Span Kind detection logic and relation to `HasRemoteParent` flag. Should + `HasRemoteParent` define span kind when `Span.Kind` was `Unspecified`? + What if `HasRemoteParent` is `true` when `Span.Kind` is `Client`? +3. Should attribute `span.kind` be respected and has priority over `SpanKind` + field? +4. When `Status` wasn't set - should it be treated as `OK` or `Unknown`? +5. ResultCode and ResponseCode calculation was implemented differently in + Local Forwarder for Request and Dependency. Is there a reason for this? +6. Why use Canonical code, not the textual representation of it? +7. When http.url is bad formed – should we store it in properties collection to + preserve an original value? +8. I don't understand why this concatenation is required for identifiers like + trace id an span id? +9. Should we recover url as https or http? +10. Will url or individual components win when looking at port, host, path? I + think individual properties conflicting with url should win. +11. Why start and end time of span are not required fields? +12. Span + [name](https://github.com/census-instrumentation/opencensus-proto/blob/ba49f56771b83cff7bea7f34d1236fc139dbc471/src/opencensus/proto/trace/v1/trace.proto#L85-L86) + is required. Does it mean that it's not empty? +13. LinkList should use `Attributes` class for consistency. \ No newline at end of file diff --git a/src/OpenCensus.Exporter.Ocagent/AssemblyInfo.cs b/src/OpenCensus.Exporter.Ocagent/AssemblyInfo.cs new file mode 100644 index 000000000..d04a05bfe --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/AssemblyInfo.cs @@ -0,0 +1,15 @@ +// +// Copyright 2018, OpenCensus 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. +// diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/ExporterOcagentEventSource.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/ExporterOcagentEventSource.cs new file mode 100644 index 000000000..4b91f93ed --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/ExporterOcagentEventSource.cs @@ -0,0 +1,63 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Ocagent.Implementation +{ + using System; + using System.Diagnostics.Tracing; + using System.Globalization; + using System.Threading; + + [EventSource(Name = "OpenCensus-Exporter-Ocagent")] + internal class ExporterOcagentEventSource : EventSource + { + public static readonly ExporterOcagentEventSource Log = new ExporterOcagentEventSource(); + + [NonEvent] + public void FailedToConvertToProtoDefinitionError(Exception ex) + { + if (Log.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.FailedToConvertToProtoDefinitionError(ToInvariantString(ex)); + } + } + + [Event(1, Message = "Exporter failed to convert SpanData content into GRPC proto definition. Data will not be sent. Exception: {0}", Level = EventLevel.Error)] + public void FailedToConvertToProtoDefinitionError(string ex) + { + this.WriteEvent(1, ex); + } + + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + private static string ToInvariantString(Exception exception) + { + CultureInfo originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/SpanDataExtentions.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/SpanDataExtentions.cs new file mode 100644 index 000000000..f4beab387 --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/SpanDataExtentions.cs @@ -0,0 +1,182 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Ocagent.Implementation +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Google.Protobuf; + using Google.Protobuf.WellKnownTypes; + + using Opencensus.Proto.Trace.V1; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + + internal static class SpanDataExtentions + { + internal static Span ToProtoSpan(this ISpanData spanData) + { + try + { + return new Span + { + Name = new TruncatableString { Value = spanData.Name }, + Kind = spanData.Kind == SpanKind.Client ? Span.Types.SpanKind.Client : Span.Types.SpanKind.Server, + TraceId = ByteString.CopyFrom(spanData.Context.TraceId.Bytes), + SpanId = ByteString.CopyFrom(spanData.Context.SpanId.Bytes), + ParentSpanId = + ByteString.CopyFrom(spanData.ParentSpanId?.Bytes ?? new byte[0]), + + StartTime = new Timestamp + { + Nanos = spanData.StartTimestamp.Nanos, + Seconds = spanData.StartTimestamp.Seconds, + }, + EndTime = new Timestamp + { + Nanos = spanData.EndTimestamp.Nanos, + Seconds = spanData.EndTimestamp.Seconds, + }, + Status = spanData.Status == null + ? null + : new Opencensus.Proto.Trace.V1.Status + { + Code = (int)spanData.Status.CanonicalCode, + Message = spanData.Status.Description ?? string.Empty, + }, + SameProcessAsParentSpan = + !spanData.HasRemoteParent.GetValueOrDefault() && spanData.ParentSpanId != null, + ChildSpanCount = spanData.ChildSpanCount.HasValue ? (uint)spanData.ChildSpanCount.Value : 0, + Attributes = FromIAttributes(spanData.Attributes), + TimeEvents = FromITimeEvents(spanData.MessageEvents, spanData.Annotations), + Links = new Span.Types.Links + { + DroppedLinksCount = spanData.Links.DroppedLinksCount, + Link = { spanData.Links.Links.Select(FromILink), }, + }, + }; + } + catch (Exception e) + { + // TODO: Is there a way to handle this better? + // This type of error processing is very aggressive and doesn't follow the + // error handling practices when smart defaults should be used when possible. + // See: https://github.com/census-instrumentation/opencensus-csharp/blob/develop/docs/error-handling.md + ExporterOcagentEventSource.Log.FailedToConvertToProtoDefinitionError(e); + } + + return null; + } + + private static Span.Types.Attributes FromIAttributes(IAttributes source) + { + var attributes = new Span.Types.Attributes + { + DroppedAttributesCount = source.DroppedAttributesCount, + }; + + attributes.AttributeMap.Add(source.AttributeMap.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Match( + s => new Opencensus.Proto.Trace.V1.AttributeValue { StringValue = new TruncatableString() { Value = s } }, + b => new Opencensus.Proto.Trace.V1.AttributeValue { BoolValue = b }, + l => new Opencensus.Proto.Trace.V1.AttributeValue { IntValue = l }, + d => new Opencensus.Proto.Trace.V1.AttributeValue { DoubleValue = d }, + o => new Opencensus.Proto.Trace.V1.AttributeValue { StringValue = new TruncatableString() { Value = o?.ToString() } }))); + + return attributes; + } + + private static Span.Types.TimeEvent FromITimeEvent(ITimedEvent source) + { + return new Span.Types.TimeEvent + { + Time = new Timestamp + { + Nanos = source.Timestamp.Nanos, + Seconds = source.Timestamp.Seconds, + }, + MessageEvent = new Span.Types.TimeEvent.Types.MessageEvent + { + Type = source.Event.Type == MessageEventType.Sent ? Span.Types.TimeEvent.Types.MessageEvent.Types.Type.Sent : Span.Types.TimeEvent.Types.MessageEvent.Types.Type.Received, + CompressedSize = (ulong)source.Event.CompressedMessageSize, + UncompressedSize = (ulong)source.Event.UncompressedMessageSize, + Id = (ulong)source.Event.MessageId, + }, + }; + } + + private static Span.Types.TimeEvents FromITimeEvents(ITimedEvents messages, ITimedEvents annotations) + { + var timedEvents = new Span.Types.TimeEvents + { + DroppedMessageEventsCount = messages.DroppedEventsCount, + DroppedAnnotationsCount = annotations.DroppedEventsCount, + TimeEvent = { messages.Events.Select(FromITimeEvent), }, + }; + + timedEvents.TimeEvent.AddRange(annotations.Events.Select(FromITimeEvent)); + + return timedEvents; + } + + private static Span.Types.Link FromILink(ILink source) + { + return new Span.Types.Link + { + TraceId = ByteString.CopyFrom(source.TraceId.Bytes), + SpanId = ByteString.CopyFrom(source.SpanId.Bytes), + Type = source.Type == LinkType.ChildLinkedSpan ? Span.Types.Link.Types.Type.ChildLinkedSpan : Span.Types.Link.Types.Type.ParentLinkedSpan, + Attributes = FromIAttributeMap(source.Attributes), + }; + } + + private static Span.Types.TimeEvent FromITimeEvent(ITimedEvent source) + { + return new Span.Types.TimeEvent + { + Time = new Timestamp + { + Nanos = source.Timestamp.Nanos, + Seconds = source.Timestamp.Seconds, + }, + Annotation = new Span.Types.TimeEvent.Types.Annotation + { + Description = new TruncatableString { Value = source.Event.Description }, + Attributes = FromIAttributeMap(source.Event.Attributes), + }, + }; + } + + private static Span.Types.Attributes FromIAttributeMap(IDictionary source) + { + var attributes = new Span.Types.Attributes(); + + attributes.AttributeMap.Add(source.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Match( + s => new Opencensus.Proto.Trace.V1.AttributeValue { StringValue = new TruncatableString() { Value = s } }, + b => new Opencensus.Proto.Trace.V1.AttributeValue { BoolValue = b }, + l => new Opencensus.Proto.Trace.V1.AttributeValue { IntValue = l }, + d => new Opencensus.Proto.Trace.V1.AttributeValue { DoubleValue = d }, + o => new Opencensus.Proto.Trace.V1.AttributeValue { StringValue = new TruncatableString() { Value = o?.ToString() } }))); + + return attributes; + } + } +} diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/TraceExporterHandler.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/TraceExporterHandler.cs new file mode 100644 index 000000000..0493e656e --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/TraceExporterHandler.cs @@ -0,0 +1,179 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Ocagent.Implementation +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Diagnostics; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + + using Google.Protobuf.WellKnownTypes; + + using Grpc.Core; + + using Opencensus.Proto.Agent.Common.V1; + using Opencensus.Proto.Agent.Trace.V1; + using OpenCensus.Trace.Export; + + internal class TraceExporterHandler : IHandler, IDisposable + { + private const uint MaxSpanBatchSize = 32; + private readonly Channel channel; + private readonly Opencensus.Proto.Agent.Trace.V1.TraceService.TraceServiceClient traceClient; + private readonly ConcurrentQueue spans = new ConcurrentQueue(); + private readonly Node node; + private readonly uint spanBatchSize; + private CancellationTokenSource cts; + private Task runTask; + + public TraceExporterHandler(string agentEndpoint, string hostName, string serviceName, ChannelCredentials credentials, uint spanBatchSize = MaxSpanBatchSize) + { + this.channel = new Channel(agentEndpoint, credentials); + this.traceClient = new TraceService.TraceServiceClient(this.channel); + this.spanBatchSize = spanBatchSize; + + this.node = new Node + { + Identifier = new ProcessIdentifier + { + HostName = hostName, + Pid = (uint)Process.GetCurrentProcess().Id, + StartTimestamp = Timestamp.FromDateTime(DateTime.UtcNow), + }, + LibraryInfo = new LibraryInfo + { + Language = LibraryInfo.Types.Language.CSharp, + CoreLibraryVersion = GetAssemblyVersion(typeof(ISpanData).Assembly), + ExporterVersion = GetAssemblyVersion(typeof(OcagentExporter).Assembly), + }, + ServiceInfo = new ServiceInfo + { + Name = serviceName, + }, + }; + + this.Start(); + } + + public async Task ExportAsync(IEnumerable spanDataList) + { + await Task.Run(() => + { + if (this.cts != null && !this.cts.IsCancellationRequested) + { + foreach (var spanData in spanDataList) + { + // TODO back-pressure on the queue + this.spans.Enqueue(spanData); + } + } + }); + } + + public void Dispose() + { + this.StopAsync().Wait(); + } + + private static string GetAssemblyVersion(Assembly assembly) + { + AssemblyFileVersionAttribute fileVersionAttr = assembly.GetCustomAttribute(); + return fileVersionAttr?.Version ?? "0.0.0"; + } + + private void Start() + { + // TODO Config + // TODO handle connection errors & retries + + this.cts = new CancellationTokenSource(); + this.runTask = this.RunAsync(this.cts.Token); + } + + private async Task StopAsync() + { + if (this.cts != null) + { + this.cts.Cancel(false); + + // ignore all exceptions + await this.runTask.ContinueWith(t => { }).ConfigureAwait(false); + + this.cts.Dispose(); + this.cts = null; + this.runTask = null; + } + } + + private async Task RunAsync(CancellationToken cancellationToken) + { + var duplexCall = this.traceClient.Export(); + try + { + bool firstRequest = true; + while (!cancellationToken.IsCancellationRequested) + { + var spanExportRequest = new ExportTraceServiceRequest(); + if (firstRequest) + { + spanExportRequest.Node = this.node; + } + + // Spans + bool hasSpans = false; + + while (spanExportRequest.Spans.Count < this.spanBatchSize) + { + if (!this.spans.TryDequeue(out var spanData)) + { + break; + } + + var protoSpan = spanData.ToProtoSpan(); + if (protoSpan == null) + { + continue; + } + + spanExportRequest.Spans.Add(protoSpan); + hasSpans = true; + } + + if (hasSpans) + { + await duplexCall.RequestStream.WriteAsync(spanExportRequest).ConfigureAwait(false); + firstRequest = false; + } + else + { + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); + } + } + + await duplexCall.RequestStream.CompleteAsync().ConfigureAwait(false); + } + catch (RpcException) + { + // TODO: log + throw; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Common.g.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Common.g.cs new file mode 100644 index 000000000..7f69b5d45 --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Common.g.cs @@ -0,0 +1,864 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: agent/common/v1/common.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Opencensus.Proto.Agent.Common.V1 { + + /// Holder for reflection information generated from agent/common/v1/common.proto + public static partial class CommonReflection { + + #region Descriptor + /// File descriptor for agent/common/v1/common.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static CommonReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "ChxhZ2VudC9jb21tb24vdjEvY29tbW9uLnByb3RvEiBvcGVuY2Vuc3VzLnBy", + "b3RvLmFnZW50LmNvbW1vbi52MRofZ29vZ2xlL3Byb3RvYnVmL3RpbWVzdGFt", + "cC5wcm90byLYAgoETm9kZRJHCgppZGVudGlmaWVyGAEgASgLMjMub3BlbmNl", + "bnN1cy5wcm90by5hZ2VudC5jb21tb24udjEuUHJvY2Vzc0lkZW50aWZpZXIS", + "QwoMbGlicmFyeV9pbmZvGAIgASgLMi0ub3BlbmNlbnN1cy5wcm90by5hZ2Vu", + "dC5jb21tb24udjEuTGlicmFyeUluZm8SQwoMc2VydmljZV9pbmZvGAMgASgL", + "Mi0ub3BlbmNlbnN1cy5wcm90by5hZ2VudC5jb21tb24udjEuU2VydmljZUlu", + "Zm8SSgoKYXR0cmlidXRlcxgEIAMoCzI2Lm9wZW5jZW5zdXMucHJvdG8uYWdl", + "bnQuY29tbW9uLnYxLk5vZGUuQXR0cmlidXRlc0VudHJ5GjEKD0F0dHJpYnV0", + "ZXNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBImgKEVBy", + "b2Nlc3NJZGVudGlmaWVyEhEKCWhvc3RfbmFtZRgBIAEoCRILCgNwaWQYAiAB", + "KA0SMwoPc3RhcnRfdGltZXN0YW1wGAMgASgLMhouZ29vZ2xlLnByb3RvYnVm", + "LlRpbWVzdGFtcCKbAgoLTGlicmFyeUluZm8SSAoIbGFuZ3VhZ2UYASABKA4y", + "Ni5vcGVuY2Vuc3VzLnByb3RvLmFnZW50LmNvbW1vbi52MS5MaWJyYXJ5SW5m", + "by5MYW5ndWFnZRIYChBleHBvcnRlcl92ZXJzaW9uGAIgASgJEhwKFGNvcmVf", + "bGlicmFyeV92ZXJzaW9uGAMgASgJIokBCghMYW5ndWFnZRIYChRMQU5HVUFH", + "RV9VTlNQRUNJRklFRBAAEgcKA0NQUBABEgsKB0NfU0hBUlAQAhIKCgZFUkxB", + "TkcQAxILCgdHT19MQU5HEAQSCAoESkFWQRAFEgsKB05PREVfSlMQBhIHCgNQ", + "SFAQBxIKCgZQWVRIT04QCBIICgRSVUJZEAkiGwoLU2VydmljZUluZm8SDAoE", + "bmFtZRgBIAEoCUJ/CiNpby5vcGVuY2Vuc3VzLnByb3RvLmFnZW50LmNvbW1v", + "bi52MUILQ29tbW9uUHJvdG9QAVpJZ2l0aHViLmNvbS9jZW5zdXMtaW5zdHJ1", + "bWVudGF0aW9uL29wZW5jZW5zdXMtcHJvdG8vZ2VuLWdvL2FnZW50L2NvbW1v", + "bi92MWIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Common.V1.Node), global::Opencensus.Proto.Agent.Common.V1.Node.Parser, new[]{ "Identifier", "LibraryInfo", "ServiceInfo", "Attributes" }, null, null, new pbr::GeneratedClrTypeInfo[] { null, }), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Common.V1.ProcessIdentifier), global::Opencensus.Proto.Agent.Common.V1.ProcessIdentifier.Parser, new[]{ "HostName", "Pid", "StartTimestamp" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Common.V1.LibraryInfo), global::Opencensus.Proto.Agent.Common.V1.LibraryInfo.Parser, new[]{ "Language", "ExporterVersion", "CoreLibraryVersion" }, null, new[]{ typeof(global::Opencensus.Proto.Agent.Common.V1.LibraryInfo.Types.Language) }, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Common.V1.ServiceInfo), global::Opencensus.Proto.Agent.Common.V1.ServiceInfo.Parser, new[]{ "Name" }, null, null, null) + })); + } + #endregion + + } + #region Messages + /// + /// Identifier metadata of the Node (Application instrumented with OpenCensus) + /// that connects to OpenCensus Agent. + /// In the future we plan to extend the identifier proto definition to support + /// additional information (e.g cloud id, etc.) + /// + public sealed partial class Node : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Node()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Common.V1.CommonReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Node() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Node(Node other) : this() { + identifier_ = other.identifier_ != null ? other.identifier_.Clone() : null; + libraryInfo_ = other.libraryInfo_ != null ? other.libraryInfo_.Clone() : null; + serviceInfo_ = other.serviceInfo_ != null ? other.serviceInfo_.Clone() : null; + attributes_ = other.attributes_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Node Clone() { + return new Node(this); + } + + /// Field number for the "identifier" field. + public const int IdentifierFieldNumber = 1; + private global::Opencensus.Proto.Agent.Common.V1.ProcessIdentifier identifier_; + /// + /// Identifier that uniquely identifies a process within a VM/container. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Agent.Common.V1.ProcessIdentifier Identifier { + get { return identifier_; } + set { + identifier_ = value; + } + } + + /// Field number for the "library_info" field. + public const int LibraryInfoFieldNumber = 2; + private global::Opencensus.Proto.Agent.Common.V1.LibraryInfo libraryInfo_; + /// + /// Information on the OpenCensus Library that initiates the stream. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Agent.Common.V1.LibraryInfo LibraryInfo { + get { return libraryInfo_; } + set { + libraryInfo_ = value; + } + } + + /// Field number for the "service_info" field. + public const int ServiceInfoFieldNumber = 3; + private global::Opencensus.Proto.Agent.Common.V1.ServiceInfo serviceInfo_; + /// + /// Additional information on service. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Agent.Common.V1.ServiceInfo ServiceInfo { + get { return serviceInfo_; } + set { + serviceInfo_ = value; + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 4; + private static readonly pbc::MapField.Codec _map_attributes_codec + = new pbc::MapField.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForString(18), 34); + private readonly pbc::MapField attributes_ = new pbc::MapField(); + /// + /// Additional attributes. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::MapField Attributes { + get { return attributes_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Node); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Node other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Identifier, other.Identifier)) return false; + if (!object.Equals(LibraryInfo, other.LibraryInfo)) return false; + if (!object.Equals(ServiceInfo, other.ServiceInfo)) return false; + if (!Attributes.Equals(other.Attributes)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (identifier_ != null) hash ^= Identifier.GetHashCode(); + if (libraryInfo_ != null) hash ^= LibraryInfo.GetHashCode(); + if (serviceInfo_ != null) hash ^= ServiceInfo.GetHashCode(); + hash ^= Attributes.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (identifier_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Identifier); + } + if (libraryInfo_ != null) { + output.WriteRawTag(18); + output.WriteMessage(LibraryInfo); + } + if (serviceInfo_ != null) { + output.WriteRawTag(26); + output.WriteMessage(ServiceInfo); + } + attributes_.WriteTo(output, _map_attributes_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (identifier_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Identifier); + } + if (libraryInfo_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(LibraryInfo); + } + if (serviceInfo_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ServiceInfo); + } + size += attributes_.CalculateSize(_map_attributes_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Node other) { + if (other == null) { + return; + } + if (other.identifier_ != null) { + if (identifier_ == null) { + identifier_ = new global::Opencensus.Proto.Agent.Common.V1.ProcessIdentifier(); + } + Identifier.MergeFrom(other.Identifier); + } + if (other.libraryInfo_ != null) { + if (libraryInfo_ == null) { + libraryInfo_ = new global::Opencensus.Proto.Agent.Common.V1.LibraryInfo(); + } + LibraryInfo.MergeFrom(other.LibraryInfo); + } + if (other.serviceInfo_ != null) { + if (serviceInfo_ == null) { + serviceInfo_ = new global::Opencensus.Proto.Agent.Common.V1.ServiceInfo(); + } + ServiceInfo.MergeFrom(other.ServiceInfo); + } + attributes_.Add(other.attributes_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (identifier_ == null) { + identifier_ = new global::Opencensus.Proto.Agent.Common.V1.ProcessIdentifier(); + } + input.ReadMessage(identifier_); + break; + } + case 18: { + if (libraryInfo_ == null) { + libraryInfo_ = new global::Opencensus.Proto.Agent.Common.V1.LibraryInfo(); + } + input.ReadMessage(libraryInfo_); + break; + } + case 26: { + if (serviceInfo_ == null) { + serviceInfo_ = new global::Opencensus.Proto.Agent.Common.V1.ServiceInfo(); + } + input.ReadMessage(serviceInfo_); + break; + } + case 34: { + attributes_.AddEntriesFrom(input, _map_attributes_codec); + break; + } + } + } + } + + } + + /// + /// Identifier that uniquely identifies a process within a VM/container. + /// + public sealed partial class ProcessIdentifier : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ProcessIdentifier()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Common.V1.CommonReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ProcessIdentifier() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ProcessIdentifier(ProcessIdentifier other) : this() { + hostName_ = other.hostName_; + pid_ = other.pid_; + startTimestamp_ = other.startTimestamp_ != null ? other.startTimestamp_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ProcessIdentifier Clone() { + return new ProcessIdentifier(this); + } + + /// Field number for the "host_name" field. + public const int HostNameFieldNumber = 1; + private string hostName_ = ""; + /// + /// The host name. Usually refers to the machine/container name. + /// For example: os.Hostname() in Go, socket.gethostname() in Python. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string HostName { + get { return hostName_; } + set { + hostName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "pid" field. + public const int PidFieldNumber = 2; + private uint pid_; + /// + /// Process id. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public uint Pid { + get { return pid_; } + set { + pid_ = value; + } + } + + /// Field number for the "start_timestamp" field. + public const int StartTimestampFieldNumber = 3; + private global::Google.Protobuf.WellKnownTypes.Timestamp startTimestamp_; + /// + /// Start time of this ProcessIdentifier. Represented in epoch time. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Google.Protobuf.WellKnownTypes.Timestamp StartTimestamp { + get { return startTimestamp_; } + set { + startTimestamp_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ProcessIdentifier); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ProcessIdentifier other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (HostName != other.HostName) return false; + if (Pid != other.Pid) return false; + if (!object.Equals(StartTimestamp, other.StartTimestamp)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (HostName.Length != 0) hash ^= HostName.GetHashCode(); + if (Pid != 0) hash ^= Pid.GetHashCode(); + if (startTimestamp_ != null) hash ^= StartTimestamp.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (HostName.Length != 0) { + output.WriteRawTag(10); + output.WriteString(HostName); + } + if (Pid != 0) { + output.WriteRawTag(16); + output.WriteUInt32(Pid); + } + if (startTimestamp_ != null) { + output.WriteRawTag(26); + output.WriteMessage(StartTimestamp); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (HostName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(HostName); + } + if (Pid != 0) { + size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Pid); + } + if (startTimestamp_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(StartTimestamp); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ProcessIdentifier other) { + if (other == null) { + return; + } + if (other.HostName.Length != 0) { + HostName = other.HostName; + } + if (other.Pid != 0) { + Pid = other.Pid; + } + if (other.startTimestamp_ != null) { + if (startTimestamp_ == null) { + startTimestamp_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + StartTimestamp.MergeFrom(other.StartTimestamp); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + HostName = input.ReadString(); + break; + } + case 16: { + Pid = input.ReadUInt32(); + break; + } + case 26: { + if (startTimestamp_ == null) { + startTimestamp_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(startTimestamp_); + break; + } + } + } + } + + } + + /// + /// Information on OpenCensus Library. + /// + public sealed partial class LibraryInfo : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new LibraryInfo()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Common.V1.CommonReflection.Descriptor.MessageTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public LibraryInfo() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public LibraryInfo(LibraryInfo other) : this() { + language_ = other.language_; + exporterVersion_ = other.exporterVersion_; + coreLibraryVersion_ = other.coreLibraryVersion_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public LibraryInfo Clone() { + return new LibraryInfo(this); + } + + /// Field number for the "language" field. + public const int LanguageFieldNumber = 1; + private global::Opencensus.Proto.Agent.Common.V1.LibraryInfo.Types.Language language_ = 0; + /// + /// Language of OpenCensus Library. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Agent.Common.V1.LibraryInfo.Types.Language Language { + get { return language_; } + set { + language_ = value; + } + } + + /// Field number for the "exporter_version" field. + public const int ExporterVersionFieldNumber = 2; + private string exporterVersion_ = ""; + /// + /// Version of Agent exporter of Library. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string ExporterVersion { + get { return exporterVersion_; } + set { + exporterVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "core_library_version" field. + public const int CoreLibraryVersionFieldNumber = 3; + private string coreLibraryVersion_ = ""; + /// + /// Version of OpenCensus Library. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string CoreLibraryVersion { + get { return coreLibraryVersion_; } + set { + coreLibraryVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as LibraryInfo); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(LibraryInfo other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Language != other.Language) return false; + if (ExporterVersion != other.ExporterVersion) return false; + if (CoreLibraryVersion != other.CoreLibraryVersion) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Language != 0) hash ^= Language.GetHashCode(); + if (ExporterVersion.Length != 0) hash ^= ExporterVersion.GetHashCode(); + if (CoreLibraryVersion.Length != 0) hash ^= CoreLibraryVersion.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Language != 0) { + output.WriteRawTag(8); + output.WriteEnum((int) Language); + } + if (ExporterVersion.Length != 0) { + output.WriteRawTag(18); + output.WriteString(ExporterVersion); + } + if (CoreLibraryVersion.Length != 0) { + output.WriteRawTag(26); + output.WriteString(CoreLibraryVersion); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Language != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Language); + } + if (ExporterVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(ExporterVersion); + } + if (CoreLibraryVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(CoreLibraryVersion); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(LibraryInfo other) { + if (other == null) { + return; + } + if (other.Language != 0) { + Language = other.Language; + } + if (other.ExporterVersion.Length != 0) { + ExporterVersion = other.ExporterVersion; + } + if (other.CoreLibraryVersion.Length != 0) { + CoreLibraryVersion = other.CoreLibraryVersion; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + language_ = (global::Opencensus.Proto.Agent.Common.V1.LibraryInfo.Types.Language) input.ReadEnum(); + break; + } + case 18: { + ExporterVersion = input.ReadString(); + break; + } + case 26: { + CoreLibraryVersion = input.ReadString(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the LibraryInfo message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + public enum Language { + [pbr::OriginalName("LANGUAGE_UNSPECIFIED")] Unspecified = 0, + [pbr::OriginalName("CPP")] Cpp = 1, + [pbr::OriginalName("C_SHARP")] CSharp = 2, + [pbr::OriginalName("ERLANG")] Erlang = 3, + [pbr::OriginalName("GO_LANG")] GoLang = 4, + [pbr::OriginalName("JAVA")] Java = 5, + [pbr::OriginalName("NODE_JS")] NodeJs = 6, + [pbr::OriginalName("PHP")] Php = 7, + [pbr::OriginalName("PYTHON")] Python = 8, + [pbr::OriginalName("RUBY")] Ruby = 9, + } + + } + #endregion + + } + + /// + /// Additional service information. + /// + public sealed partial class ServiceInfo : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ServiceInfo()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Common.V1.CommonReflection.Descriptor.MessageTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ServiceInfo() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ServiceInfo(ServiceInfo other) : this() { + name_ = other.name_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ServiceInfo Clone() { + return new ServiceInfo(this); + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 1; + private string name_ = ""; + /// + /// Name of the service. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ServiceInfo); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ServiceInfo other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Name != other.Name) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Name.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Name); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ServiceInfo other) { + if (other == null) { + return; + } + if (other.Name.Length != 0) { + Name = other.Name; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Name = input.ReadString(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Resource.g.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Resource.g.cs new file mode 100644 index 000000000..85712ca5f --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Resource.g.cs @@ -0,0 +1,207 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: resource/v1/resource.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Opencensus.Proto.Resource.V1 { + + /// Holder for reflection information generated from resource/v1/resource.proto + public static partial class ResourceReflection { + + #region Descriptor + /// File descriptor for resource/v1/resource.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ResourceReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "ChpyZXNvdXJjZS92MS9yZXNvdXJjZS5wcm90bxIcb3BlbmNlbnN1cy5wcm90", + "by5yZXNvdXJjZS52MSKLAQoIUmVzb3VyY2USDAoEdHlwZRgBIAEoCRJCCgZs", + "YWJlbHMYAiADKAsyMi5vcGVuY2Vuc3VzLnByb3RvLnJlc291cmNlLnYxLlJl", + "c291cmNlLkxhYmVsc0VudHJ5Gi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEo", + "CRINCgV2YWx1ZRgCIAEoCToCOAFCeQofaW8ub3BlbmNlbnN1cy5wcm90by5y", + "ZXNvdXJjZS52MUINUmVzb3VyY2VQcm90b1ABWkVnaXRodWIuY29tL2NlbnN1", + "cy1pbnN0cnVtZW50YXRpb24vb3BlbmNlbnN1cy1wcm90by9nZW4tZ28vcmVz", + "b3VyY2UvdjFiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Resource.V1.Resource), global::Opencensus.Proto.Resource.V1.Resource.Parser, new[]{ "Type", "Labels" }, null, null, new pbr::GeneratedClrTypeInfo[] { null, }) + })); + } + #endregion + + } + #region Messages + /// + /// Resource information. + /// + public sealed partial class Resource : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Resource()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Resource.V1.ResourceReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Resource() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Resource(Resource other) : this() { + type_ = other.type_; + labels_ = other.labels_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Resource Clone() { + return new Resource(this); + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 1; + private string type_ = ""; + /// + /// Type identifier for the resource. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Type { + get { return type_; } + set { + type_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "labels" field. + public const int LabelsFieldNumber = 2; + private static readonly pbc::MapField.Codec _map_labels_codec + = new pbc::MapField.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForString(18), 18); + private readonly pbc::MapField labels_ = new pbc::MapField(); + /// + /// Set of labels that describe the resource. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::MapField Labels { + get { return labels_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Resource); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Resource other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Type != other.Type) return false; + if (!Labels.Equals(other.Labels)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Type.Length != 0) hash ^= Type.GetHashCode(); + hash ^= Labels.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Type.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Type); + } + labels_.WriteTo(output, _map_labels_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Type.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Type); + } + size += labels_.CalculateSize(_map_labels_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Resource other) { + if (other == null) { + return; + } + if (other.Type.Length != 0) { + Type = other.Type; + } + labels_.Add(other.labels_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Type = input.ReadString(); + break; + } + case 18: { + labels_.AddEntriesFrom(input, _map_labels_codec); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Trace.g.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Trace.g.cs new file mode 100644 index 000000000..b97d25312 --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/Trace.g.cs @@ -0,0 +1,4106 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: trace/v1/trace.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Opencensus.Proto.Trace.V1 { + + /// Holder for reflection information generated from trace/v1/trace.proto + public static partial class TraceReflection { + + #region Descriptor + /// File descriptor for trace/v1/trace.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static TraceReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "ChR0cmFjZS92MS90cmFjZS5wcm90bxIZb3BlbmNlbnN1cy5wcm90by50cmFj", + "ZS52MRofZ29vZ2xlL3Byb3RvYnVmL3RpbWVzdGFtcC5wcm90bxoeZ29vZ2xl", + "L3Byb3RvYnVmL3dyYXBwZXJzLnByb3RvIrIRCgRTcGFuEhAKCHRyYWNlX2lk", + "GAEgASgMEg8KB3NwYW5faWQYAiABKAwSPgoKdHJhY2VzdGF0ZRgPIAEoCzIq", + "Lm9wZW5jZW5zdXMucHJvdG8udHJhY2UudjEuU3Bhbi5UcmFjZXN0YXRlEhYK", + "DnBhcmVudF9zcGFuX2lkGAMgASgMEjoKBG5hbWUYBCABKAsyLC5vcGVuY2Vu", + "c3VzLnByb3RvLnRyYWNlLnYxLlRydW5jYXRhYmxlU3RyaW5nEjYKBGtpbmQY", + "DiABKA4yKC5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLlNwYW4uU3Bhbktp", + "bmQSLgoKc3RhcnRfdGltZRgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1l", + "c3RhbXASLAoIZW5kX3RpbWUYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGlt", + "ZXN0YW1wEj4KCmF0dHJpYnV0ZXMYByABKAsyKi5vcGVuY2Vuc3VzLnByb3Rv", + "LnRyYWNlLnYxLlNwYW4uQXR0cmlidXRlcxI6CgtzdGFja190cmFjZRgIIAEo", + "CzIlLm9wZW5jZW5zdXMucHJvdG8udHJhY2UudjEuU3RhY2tUcmFjZRI/Cgt0", + "aW1lX2V2ZW50cxgJIAEoCzIqLm9wZW5jZW5zdXMucHJvdG8udHJhY2UudjEu", + "U3Bhbi5UaW1lRXZlbnRzEjQKBWxpbmtzGAogASgLMiUub3BlbmNlbnN1cy5w", + "cm90by50cmFjZS52MS5TcGFuLkxpbmtzEjEKBnN0YXR1cxgLIAEoCzIhLm9w", + "ZW5jZW5zdXMucHJvdG8udHJhY2UudjEuU3RhdHVzEj8KG3NhbWVfcHJvY2Vz", + "c19hc19wYXJlbnRfc3BhbhgMIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5Cb29s", + "VmFsdWUSNgoQY2hpbGRfc3Bhbl9jb3VudBgNIAEoCzIcLmdvb2dsZS5wcm90", + "b2J1Zi5VSW50MzJWYWx1ZRp0CgpUcmFjZXN0YXRlEkEKB2VudHJpZXMYASAD", + "KAsyMC5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLlNwYW4uVHJhY2VzdGF0", + "ZS5FbnRyeRojCgVFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAka", + "4wEKCkF0dHJpYnV0ZXMSUwoNYXR0cmlidXRlX21hcBgBIAMoCzI8Lm9wZW5j", + "ZW5zdXMucHJvdG8udHJhY2UudjEuU3Bhbi5BdHRyaWJ1dGVzLkF0dHJpYnV0", + "ZU1hcEVudHJ5EiAKGGRyb3BwZWRfYXR0cmlidXRlc19jb3VudBgCIAEoBRpe", + "ChFBdHRyaWJ1dGVNYXBFbnRyeRILCgNrZXkYASABKAkSOAoFdmFsdWUYAiAB", + "KAsyKS5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLkF0dHJpYnV0ZVZhbHVl", + "OgI4ARq/BAoJVGltZUV2ZW50EigKBHRpbWUYASABKAsyGi5nb29nbGUucHJv", + "dG9idWYuVGltZXN0YW1wEkoKCmFubm90YXRpb24YAiABKAsyNC5vcGVuY2Vu", + "c3VzLnByb3RvLnRyYWNlLnYxLlNwYW4uVGltZUV2ZW50LkFubm90YXRpb25I", + "ABJPCg1tZXNzYWdlX2V2ZW50GAMgASgLMjYub3BlbmNlbnN1cy5wcm90by50", + "cmFjZS52MS5TcGFuLlRpbWVFdmVudC5NZXNzYWdlRXZlbnRIABqPAQoKQW5u", + "b3RhdGlvbhJBCgtkZXNjcmlwdGlvbhgBIAEoCzIsLm9wZW5jZW5zdXMucHJv", + "dG8udHJhY2UudjEuVHJ1bmNhdGFibGVTdHJpbmcSPgoKYXR0cmlidXRlcxgC", + "IAEoCzIqLm9wZW5jZW5zdXMucHJvdG8udHJhY2UudjEuU3Bhbi5BdHRyaWJ1", + "dGVzGs8BCgxNZXNzYWdlRXZlbnQSSQoEdHlwZRgBIAEoDjI7Lm9wZW5jZW5z", + "dXMucHJvdG8udHJhY2UudjEuU3Bhbi5UaW1lRXZlbnQuTWVzc2FnZUV2ZW50", + "LlR5cGUSCgoCaWQYAiABKAQSGQoRdW5jb21wcmVzc2VkX3NpemUYAyABKAQS", + "FwoPY29tcHJlc3NlZF9zaXplGAQgASgEIjQKBFR5cGUSFAoQVFlQRV9VTlNQ", + "RUNJRklFRBAAEggKBFNFTlQQARIMCghSRUNFSVZFRBACQgcKBXZhbHVlGpQB", + "CgpUaW1lRXZlbnRzEj0KCnRpbWVfZXZlbnQYASADKAsyKS5vcGVuY2Vuc3Vz", + "LnByb3RvLnRyYWNlLnYxLlNwYW4uVGltZUV2ZW50EiEKGWRyb3BwZWRfYW5u", + "b3RhdGlvbnNfY291bnQYAiABKAUSJAocZHJvcHBlZF9tZXNzYWdlX2V2ZW50", + "c19jb3VudBgDIAEoBRrvAQoETGluaxIQCgh0cmFjZV9pZBgBIAEoDBIPCgdz", + "cGFuX2lkGAIgASgMEjcKBHR5cGUYAyABKA4yKS5vcGVuY2Vuc3VzLnByb3Rv", + "LnRyYWNlLnYxLlNwYW4uTGluay5UeXBlEj4KCmF0dHJpYnV0ZXMYBCABKAsy", + "Ki5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLlNwYW4uQXR0cmlidXRlcyJL", + "CgRUeXBlEhQKEFRZUEVfVU5TUEVDSUZJRUQQABIVChFDSElMRF9MSU5LRURf", + "U1BBThABEhYKElBBUkVOVF9MSU5LRURfU1BBThACGlgKBUxpbmtzEjIKBGxp", + "bmsYASADKAsyJC5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLlNwYW4uTGlu", + "axIbChNkcm9wcGVkX2xpbmtzX2NvdW50GAIgASgFIj0KCFNwYW5LaW5kEhkK", + "FVNQQU5fS0lORF9VTlNQRUNJRklFRBAAEgoKBlNFUlZFUhABEgoKBkNMSUVO", + "VBACIicKBlN0YXR1cxIMCgRjb2RlGAEgASgFEg8KB21lc3NhZ2UYAiABKAki", + "ogEKDkF0dHJpYnV0ZVZhbHVlEkQKDHN0cmluZ192YWx1ZRgBIAEoCzIsLm9w", + "ZW5jZW5zdXMucHJvdG8udHJhY2UudjEuVHJ1bmNhdGFibGVTdHJpbmdIABIT", + "CglpbnRfdmFsdWUYAiABKANIABIUCgpib29sX3ZhbHVlGAMgASgISAASFgoM", + "ZG91YmxlX3ZhbHVlGAQgASgBSABCBwoFdmFsdWUi7QQKClN0YWNrVHJhY2US", + "RwoMc3RhY2tfZnJhbWVzGAEgASgLMjEub3BlbmNlbnN1cy5wcm90by50cmFj", + "ZS52MS5TdGFja1RyYWNlLlN0YWNrRnJhbWVzEhsKE3N0YWNrX3RyYWNlX2hh", + "c2hfaWQYAiABKAQaigMKClN0YWNrRnJhbWUSQwoNZnVuY3Rpb25fbmFtZRgB", + "IAEoCzIsLm9wZW5jZW5zdXMucHJvdG8udHJhY2UudjEuVHJ1bmNhdGFibGVT", + "dHJpbmcSTAoWb3JpZ2luYWxfZnVuY3Rpb25fbmFtZRgCIAEoCzIsLm9wZW5j", + "ZW5zdXMucHJvdG8udHJhY2UudjEuVHJ1bmNhdGFibGVTdHJpbmcSPwoJZmls", + "ZV9uYW1lGAMgASgLMiwub3BlbmNlbnN1cy5wcm90by50cmFjZS52MS5UcnVu", + "Y2F0YWJsZVN0cmluZxITCgtsaW5lX251bWJlchgEIAEoAxIVCg1jb2x1bW5f", + "bnVtYmVyGAUgASgDEjYKC2xvYWRfbW9kdWxlGAYgASgLMiEub3BlbmNlbnN1", + "cy5wcm90by50cmFjZS52MS5Nb2R1bGUSRAoOc291cmNlX3ZlcnNpb24YByAB", + "KAsyLC5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLlRydW5jYXRhYmxlU3Ry", + "aW5nGmwKC1N0YWNrRnJhbWVzEj8KBWZyYW1lGAEgAygLMjAub3BlbmNlbnN1", + "cy5wcm90by50cmFjZS52MS5TdGFja1RyYWNlLlN0YWNrRnJhbWUSHAoUZHJv", + "cHBlZF9mcmFtZXNfY291bnQYAiABKAUihgEKBk1vZHVsZRI8CgZtb2R1bGUY", + "ASABKAsyLC5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLlRydW5jYXRhYmxl", + "U3RyaW5nEj4KCGJ1aWxkX2lkGAIgASgLMiwub3BlbmNlbnN1cy5wcm90by50", + "cmFjZS52MS5UcnVuY2F0YWJsZVN0cmluZyJAChFUcnVuY2F0YWJsZVN0cmlu", + "ZxINCgV2YWx1ZRgBIAEoCRIcChR0cnVuY2F0ZWRfYnl0ZV9jb3VudBgCIAEo", + "BUJwChxpby5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxQgpUcmFjZVByb3Rv", + "UAFaQmdpdGh1Yi5jb20vY2Vuc3VzLWluc3RydW1lbnRhdGlvbi9vcGVuY2Vu", + "c3VzLXByb3RvL2dlbi1nby90cmFjZS92MWIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span), global::Opencensus.Proto.Trace.V1.Span.Parser, new[]{ "TraceId", "SpanId", "Tracestate", "ParentSpanId", "Name", "Kind", "StartTime", "EndTime", "Attributes", "StackTrace", "TimeEvents", "Links", "Status", "SameProcessAsParentSpan", "ChildSpanCount" }, null, new[]{ typeof(global::Opencensus.Proto.Trace.V1.Span.Types.SpanKind) }, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate), global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate.Parser, new[]{ "Entries" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate.Types.Entry), global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate.Types.Entry.Parser, new[]{ "Key", "Value" }, null, null, null)}), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.Attributes), global::Opencensus.Proto.Trace.V1.Span.Types.Attributes.Parser, new[]{ "AttributeMap", "DroppedAttributesCount" }, null, null, new pbr::GeneratedClrTypeInfo[] { null, }), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent), global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Parser, new[]{ "Time", "Annotation", "MessageEvent" }, new[]{ "Value" }, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.Annotation), global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.Annotation.Parser, new[]{ "Description", "Attributes" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent), global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent.Parser, new[]{ "Type", "Id", "UncompressedSize", "CompressedSize" }, null, new[]{ typeof(global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent.Types.Type) }, null)}), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvents), global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvents.Parser, new[]{ "TimeEvent", "DroppedAnnotationsCount", "DroppedMessageEventsCount" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.Link), global::Opencensus.Proto.Trace.V1.Span.Types.Link.Parser, new[]{ "TraceId", "SpanId", "Type", "Attributes" }, null, new[]{ typeof(global::Opencensus.Proto.Trace.V1.Span.Types.Link.Types.Type) }, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Span.Types.Links), global::Opencensus.Proto.Trace.V1.Span.Types.Links.Parser, new[]{ "Link", "DroppedLinksCount" }, null, null, null)}), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Status), global::Opencensus.Proto.Trace.V1.Status.Parser, new[]{ "Code", "Message" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.AttributeValue), global::Opencensus.Proto.Trace.V1.AttributeValue.Parser, new[]{ "StringValue", "IntValue", "BoolValue", "DoubleValue" }, new[]{ "Value" }, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.StackTrace), global::Opencensus.Proto.Trace.V1.StackTrace.Parser, new[]{ "StackFrames", "StackTraceHashId" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrame), global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrame.Parser, new[]{ "FunctionName", "OriginalFunctionName", "FileName", "LineNumber", "ColumnNumber", "LoadModule", "SourceVersion" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrames), global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrames.Parser, new[]{ "Frame", "DroppedFramesCount" }, null, null, null)}), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.Module), global::Opencensus.Proto.Trace.V1.Module.Parser, new[]{ "Module_", "BuildId" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.TruncatableString), global::Opencensus.Proto.Trace.V1.TruncatableString.Parser, new[]{ "Value", "TruncatedByteCount" }, null, null, null) + })); + } + #endregion + + } + #region Messages + /// + /// A span represents a single operation within a trace. Spans can be + /// nested to form a trace tree. Often, a trace contains a root span + /// that describes the end-to-end latency, and one or more subspans for + /// its sub-operations. A trace can also contain multiple root spans, + /// or none at all. Spans do not need to be contiguous - there may be + /// gaps or overlaps between spans in a trace. + /// + /// The next id is 16. + /// TODO(bdrutu): Add an example. + /// + public sealed partial class Span : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Span()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Span() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Span(Span other) : this() { + traceId_ = other.traceId_; + spanId_ = other.spanId_; + tracestate_ = other.tracestate_ != null ? other.tracestate_.Clone() : null; + parentSpanId_ = other.parentSpanId_; + name_ = other.name_ != null ? other.name_.Clone() : null; + kind_ = other.kind_; + startTime_ = other.startTime_ != null ? other.startTime_.Clone() : null; + endTime_ = other.endTime_ != null ? other.endTime_.Clone() : null; + attributes_ = other.attributes_ != null ? other.attributes_.Clone() : null; + stackTrace_ = other.stackTrace_ != null ? other.stackTrace_.Clone() : null; + timeEvents_ = other.timeEvents_ != null ? other.timeEvents_.Clone() : null; + links_ = other.links_ != null ? other.links_.Clone() : null; + status_ = other.status_ != null ? other.status_.Clone() : null; + SameProcessAsParentSpan = other.SameProcessAsParentSpan; + ChildSpanCount = other.ChildSpanCount; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Span Clone() { + return new Span(this); + } + + /// Field number for the "trace_id" field. + public const int TraceIdFieldNumber = 1; + private pb::ByteString traceId_ = pb::ByteString.Empty; + /// + /// A unique identifier for a trace. All spans from the same trace share + /// the same `trace_id`. The ID is a 16-byte array. + /// + /// This field is required. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString TraceId { + get { return traceId_; } + set { + traceId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "span_id" field. + public const int SpanIdFieldNumber = 2; + private pb::ByteString spanId_ = pb::ByteString.Empty; + /// + /// A unique identifier for a span within a trace, assigned when the span + /// is created. The ID is an 8-byte array. + /// + /// This field is required. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString SpanId { + get { return spanId_; } + set { + spanId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "tracestate" field. + public const int TracestateFieldNumber = 15; + private global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate tracestate_; + /// + /// The Tracestate on the span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate Tracestate { + get { return tracestate_; } + set { + tracestate_ = value; + } + } + + /// Field number for the "parent_span_id" field. + public const int ParentSpanIdFieldNumber = 3; + private pb::ByteString parentSpanId_ = pb::ByteString.Empty; + /// + /// The `span_id` of this span's parent span. If this is a root span, then this + /// field must be empty. The ID is an 8-byte array. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString ParentSpanId { + get { return parentSpanId_; } + set { + parentSpanId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 4; + private global::Opencensus.Proto.Trace.V1.TruncatableString name_; + /// + /// A description of the span's operation. + /// + /// For example, the name can be a qualified method name or a file name + /// and a line number where the operation is called. A best practice is to use + /// the same display name at the same call point in an application. + /// This makes it easier to correlate spans in different traces. + /// + /// This field is required. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString Name { + get { return name_; } + set { + name_ = value; + } + } + + /// Field number for the "kind" field. + public const int KindFieldNumber = 14; + private global::Opencensus.Proto.Trace.V1.Span.Types.SpanKind kind_ = 0; + /// + /// Distinguishes between spans generated in a particular context. For example, + /// two spans with the same name may be distinguished using `CLIENT` + /// and `SERVER` to identify queueing latency associated with the span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.SpanKind Kind { + get { return kind_; } + set { + kind_ = value; + } + } + + /// Field number for the "start_time" field. + public const int StartTimeFieldNumber = 5; + private global::Google.Protobuf.WellKnownTypes.Timestamp startTime_; + /// + /// The start time of the span. On the client side, this is the time kept by + /// the local machine where the span execution starts. On the server side, this + /// is the time when the server's application handler starts running. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Google.Protobuf.WellKnownTypes.Timestamp StartTime { + get { return startTime_; } + set { + startTime_ = value; + } + } + + /// Field number for the "end_time" field. + public const int EndTimeFieldNumber = 6; + private global::Google.Protobuf.WellKnownTypes.Timestamp endTime_; + /// + /// The end time of the span. On the client side, this is the time kept by + /// the local machine where the span execution ends. On the server side, this + /// is the time when the server application handler stops running. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Google.Protobuf.WellKnownTypes.Timestamp EndTime { + get { return endTime_; } + set { + endTime_ = value; + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 7; + private global::Opencensus.Proto.Trace.V1.Span.Types.Attributes attributes_; + /// + /// A set of attributes on the span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.Attributes Attributes { + get { return attributes_; } + set { + attributes_ = value; + } + } + + /// Field number for the "stack_trace" field. + public const int StackTraceFieldNumber = 8; + private global::Opencensus.Proto.Trace.V1.StackTrace stackTrace_; + /// + /// A stack trace captured at the start of the span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.StackTrace StackTrace { + get { return stackTrace_; } + set { + stackTrace_ = value; + } + } + + /// Field number for the "time_events" field. + public const int TimeEventsFieldNumber = 9; + private global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvents timeEvents_; + /// + /// The included time events. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvents TimeEvents { + get { return timeEvents_; } + set { + timeEvents_ = value; + } + } + + /// Field number for the "links" field. + public const int LinksFieldNumber = 10; + private global::Opencensus.Proto.Trace.V1.Span.Types.Links links_; + /// + /// The included links. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.Links Links { + get { return links_; } + set { + links_ = value; + } + } + + /// Field number for the "status" field. + public const int StatusFieldNumber = 11; + private global::Opencensus.Proto.Trace.V1.Status status_; + /// + /// An optional final status for this span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Status Status { + get { return status_; } + set { + status_ = value; + } + } + + /// Field number for the "same_process_as_parent_span" field. + public const int SameProcessAsParentSpanFieldNumber = 12; + private static readonly pb::FieldCodec _single_sameProcessAsParentSpan_codec = pb::FieldCodec.ForStructWrapper(98); + private bool? sameProcessAsParentSpan_; + /// + /// A highly recommended but not required flag that identifies when a trace + /// crosses a process boundary. True when the parent_span belongs to the + /// same process as the current span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool? SameProcessAsParentSpan { + get { return sameProcessAsParentSpan_; } + set { + sameProcessAsParentSpan_ = value; + } + } + + /// Field number for the "child_span_count" field. + public const int ChildSpanCountFieldNumber = 13; + private static readonly pb::FieldCodec _single_childSpanCount_codec = pb::FieldCodec.ForStructWrapper(106); + private uint? childSpanCount_; + /// + /// An optional number of child spans that were generated while this span + /// was active. If set, allows an implementation to detect missing child spans. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public uint? ChildSpanCount { + get { return childSpanCount_; } + set { + childSpanCount_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Span); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Span other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (TraceId != other.TraceId) return false; + if (SpanId != other.SpanId) return false; + if (!object.Equals(Tracestate, other.Tracestate)) return false; + if (ParentSpanId != other.ParentSpanId) return false; + if (!object.Equals(Name, other.Name)) return false; + if (Kind != other.Kind) return false; + if (!object.Equals(StartTime, other.StartTime)) return false; + if (!object.Equals(EndTime, other.EndTime)) return false; + if (!object.Equals(Attributes, other.Attributes)) return false; + if (!object.Equals(StackTrace, other.StackTrace)) return false; + if (!object.Equals(TimeEvents, other.TimeEvents)) return false; + if (!object.Equals(Links, other.Links)) return false; + if (!object.Equals(Status, other.Status)) return false; + if (SameProcessAsParentSpan != other.SameProcessAsParentSpan) return false; + if (ChildSpanCount != other.ChildSpanCount) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (TraceId.Length != 0) hash ^= TraceId.GetHashCode(); + if (SpanId.Length != 0) hash ^= SpanId.GetHashCode(); + if (tracestate_ != null) hash ^= Tracestate.GetHashCode(); + if (ParentSpanId.Length != 0) hash ^= ParentSpanId.GetHashCode(); + if (name_ != null) hash ^= Name.GetHashCode(); + if (Kind != 0) hash ^= Kind.GetHashCode(); + if (startTime_ != null) hash ^= StartTime.GetHashCode(); + if (endTime_ != null) hash ^= EndTime.GetHashCode(); + if (attributes_ != null) hash ^= Attributes.GetHashCode(); + if (stackTrace_ != null) hash ^= StackTrace.GetHashCode(); + if (timeEvents_ != null) hash ^= TimeEvents.GetHashCode(); + if (links_ != null) hash ^= Links.GetHashCode(); + if (status_ != null) hash ^= Status.GetHashCode(); + if (sameProcessAsParentSpan_ != null) hash ^= SameProcessAsParentSpan.GetHashCode(); + if (childSpanCount_ != null) hash ^= ChildSpanCount.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (TraceId.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(TraceId); + } + if (SpanId.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(SpanId); + } + if (ParentSpanId.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(ParentSpanId); + } + if (name_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Name); + } + if (startTime_ != null) { + output.WriteRawTag(42); + output.WriteMessage(StartTime); + } + if (endTime_ != null) { + output.WriteRawTag(50); + output.WriteMessage(EndTime); + } + if (attributes_ != null) { + output.WriteRawTag(58); + output.WriteMessage(Attributes); + } + if (stackTrace_ != null) { + output.WriteRawTag(66); + output.WriteMessage(StackTrace); + } + if (timeEvents_ != null) { + output.WriteRawTag(74); + output.WriteMessage(TimeEvents); + } + if (links_ != null) { + output.WriteRawTag(82); + output.WriteMessage(Links); + } + if (status_ != null) { + output.WriteRawTag(90); + output.WriteMessage(Status); + } + if (sameProcessAsParentSpan_ != null) { + _single_sameProcessAsParentSpan_codec.WriteTagAndValue(output, SameProcessAsParentSpan); + } + if (childSpanCount_ != null) { + _single_childSpanCount_codec.WriteTagAndValue(output, ChildSpanCount); + } + if (Kind != 0) { + output.WriteRawTag(112); + output.WriteEnum((int) Kind); + } + if (tracestate_ != null) { + output.WriteRawTag(122); + output.WriteMessage(Tracestate); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (TraceId.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(TraceId); + } + if (SpanId.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SpanId); + } + if (tracestate_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Tracestate); + } + if (ParentSpanId.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(ParentSpanId); + } + if (name_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Name); + } + if (Kind != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Kind); + } + if (startTime_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(StartTime); + } + if (endTime_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EndTime); + } + if (attributes_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Attributes); + } + if (stackTrace_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(StackTrace); + } + if (timeEvents_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TimeEvents); + } + if (links_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Links); + } + if (status_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Status); + } + if (sameProcessAsParentSpan_ != null) { + size += _single_sameProcessAsParentSpan_codec.CalculateSizeWithTag(SameProcessAsParentSpan); + } + if (childSpanCount_ != null) { + size += _single_childSpanCount_codec.CalculateSizeWithTag(ChildSpanCount); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Span other) { + if (other == null) { + return; + } + if (other.TraceId.Length != 0) { + TraceId = other.TraceId; + } + if (other.SpanId.Length != 0) { + SpanId = other.SpanId; + } + if (other.tracestate_ != null) { + if (tracestate_ == null) { + tracestate_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate(); + } + Tracestate.MergeFrom(other.Tracestate); + } + if (other.ParentSpanId.Length != 0) { + ParentSpanId = other.ParentSpanId; + } + if (other.name_ != null) { + if (name_ == null) { + name_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + Name.MergeFrom(other.Name); + } + if (other.Kind != 0) { + Kind = other.Kind; + } + if (other.startTime_ != null) { + if (startTime_ == null) { + startTime_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + StartTime.MergeFrom(other.StartTime); + } + if (other.endTime_ != null) { + if (endTime_ == null) { + endTime_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + EndTime.MergeFrom(other.EndTime); + } + if (other.attributes_ != null) { + if (attributes_ == null) { + attributes_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Attributes(); + } + Attributes.MergeFrom(other.Attributes); + } + if (other.stackTrace_ != null) { + if (stackTrace_ == null) { + stackTrace_ = new global::Opencensus.Proto.Trace.V1.StackTrace(); + } + StackTrace.MergeFrom(other.StackTrace); + } + if (other.timeEvents_ != null) { + if (timeEvents_ == null) { + timeEvents_ = new global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvents(); + } + TimeEvents.MergeFrom(other.TimeEvents); + } + if (other.links_ != null) { + if (links_ == null) { + links_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Links(); + } + Links.MergeFrom(other.Links); + } + if (other.status_ != null) { + if (status_ == null) { + status_ = new global::Opencensus.Proto.Trace.V1.Status(); + } + Status.MergeFrom(other.Status); + } + if (other.sameProcessAsParentSpan_ != null) { + if (sameProcessAsParentSpan_ == null || other.SameProcessAsParentSpan != false) { + SameProcessAsParentSpan = other.SameProcessAsParentSpan; + } + } + if (other.childSpanCount_ != null) { + if (childSpanCount_ == null || other.ChildSpanCount != 0) { + ChildSpanCount = other.ChildSpanCount; + } + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + TraceId = input.ReadBytes(); + break; + } + case 18: { + SpanId = input.ReadBytes(); + break; + } + case 26: { + ParentSpanId = input.ReadBytes(); + break; + } + case 34: { + if (name_ == null) { + name_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(name_); + break; + } + case 42: { + if (startTime_ == null) { + startTime_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(startTime_); + break; + } + case 50: { + if (endTime_ == null) { + endTime_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(endTime_); + break; + } + case 58: { + if (attributes_ == null) { + attributes_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Attributes(); + } + input.ReadMessage(attributes_); + break; + } + case 66: { + if (stackTrace_ == null) { + stackTrace_ = new global::Opencensus.Proto.Trace.V1.StackTrace(); + } + input.ReadMessage(stackTrace_); + break; + } + case 74: { + if (timeEvents_ == null) { + timeEvents_ = new global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvents(); + } + input.ReadMessage(timeEvents_); + break; + } + case 82: { + if (links_ == null) { + links_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Links(); + } + input.ReadMessage(links_); + break; + } + case 90: { + if (status_ == null) { + status_ = new global::Opencensus.Proto.Trace.V1.Status(); + } + input.ReadMessage(status_); + break; + } + case 98: { + bool? value = _single_sameProcessAsParentSpan_codec.Read(input); + if (sameProcessAsParentSpan_ == null || value != false) { + SameProcessAsParentSpan = value; + } + break; + } + case 106: { + uint? value = _single_childSpanCount_codec.Read(input); + if (childSpanCount_ == null || value != 0) { + ChildSpanCount = value; + } + break; + } + case 112: { + kind_ = (global::Opencensus.Proto.Trace.V1.Span.Types.SpanKind) input.ReadEnum(); + break; + } + case 122: { + if (tracestate_ == null) { + tracestate_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate(); + } + input.ReadMessage(tracestate_); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the Span message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + /// + /// Type of span. Can be used to specify additional relationships between spans + /// in addition to a parent/child relationship. + /// + public enum SpanKind { + /// + /// Unspecified. + /// + [pbr::OriginalName("SPAN_KIND_UNSPECIFIED")] Unspecified = 0, + /// + /// Indicates that the span covers server-side handling of an RPC or other + /// remote network request. + /// + [pbr::OriginalName("SERVER")] Server = 1, + /// + /// Indicates that the span covers the client-side wrapper around an RPC or + /// other remote request. + /// + [pbr::OriginalName("CLIENT")] Client = 2, + } + + /// + /// This field conveys information about request position in multiple distributed tracing graphs. + /// It is a list of Tracestate.Entry with a maximum of 32 members in the list. + /// + /// See the https://github.com/w3c/distributed-tracing for more details about this field. + /// + public sealed partial class Tracestate : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Tracestate()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Descriptor.NestedTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Tracestate() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Tracestate(Tracestate other) : this() { + entries_ = other.entries_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Tracestate Clone() { + return new Tracestate(this); + } + + /// Field number for the "entries" field. + public const int EntriesFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_entries_codec + = pb::FieldCodec.ForMessage(10, global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate.Types.Entry.Parser); + private readonly pbc::RepeatedField entries_ = new pbc::RepeatedField(); + /// + /// A list of entries that represent the Tracestate. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Entries { + get { return entries_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Tracestate); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Tracestate other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!entries_.Equals(other.entries_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= entries_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + entries_.WriteTo(output, _repeated_entries_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += entries_.CalculateSize(_repeated_entries_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Tracestate other) { + if (other == null) { + return; + } + entries_.Add(other.entries_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + entries_.AddEntriesFrom(input, _repeated_entries_codec); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the Tracestate message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + public sealed partial class Entry : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Entry()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Types.Tracestate.Descriptor.NestedTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Entry() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Entry(Entry other) : this() { + key_ = other.key_; + value_ = other.value_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Entry Clone() { + return new Entry(this); + } + + /// Field number for the "key" field. + public const int KeyFieldNumber = 1; + private string key_ = ""; + /// + /// The key must begin with a lowercase letter, and can only contain + /// lowercase letters 'a'-'z', digits '0'-'9', underscores '_', dashes + /// '-', asterisks '*', and forward slashes '/'. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Key { + get { return key_; } + set { + key_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 2; + private string value_ = ""; + /// + /// The value is opaque string up to 256 characters printable ASCII + /// RFC0020 characters (i.e., the range 0x20 to 0x7E) except ',' and '='. + /// Note that this also excludes tabs, newlines, carriage returns, etc. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Value { + get { return value_; } + set { + value_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Entry); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Entry other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Key != other.Key) return false; + if (Value != other.Value) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Key.Length != 0) hash ^= Key.GetHashCode(); + if (Value.Length != 0) hash ^= Value.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Key.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Key); + } + if (Value.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Value); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Key.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Key); + } + if (Value.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Value); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Entry other) { + if (other == null) { + return; + } + if (other.Key.Length != 0) { + Key = other.Key; + } + if (other.Value.Length != 0) { + Value = other.Value; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Key = input.ReadString(); + break; + } + case 18: { + Value = input.ReadString(); + break; + } + } + } + } + + } + + } + #endregion + + } + + /// + /// A set of attributes, each with a key and a value. + /// + public sealed partial class Attributes : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Attributes()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Descriptor.NestedTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Attributes() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Attributes(Attributes other) : this() { + attributeMap_ = other.attributeMap_.Clone(); + droppedAttributesCount_ = other.droppedAttributesCount_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Attributes Clone() { + return new Attributes(this); + } + + /// Field number for the "attribute_map" field. + public const int AttributeMapFieldNumber = 1; + private static readonly pbc::MapField.Codec _map_attributeMap_codec + = new pbc::MapField.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForMessage(18, global::Opencensus.Proto.Trace.V1.AttributeValue.Parser), 10); + private readonly pbc::MapField attributeMap_ = new pbc::MapField(); + /// + /// The set of attributes. The value can be a string, an integer, or the + /// Boolean values `true` and `false`. For example: + /// + /// "/instance_id": "my-instance" + /// "/http/user_agent": "" + /// "/http/server_latency": 300 + /// "abc.com/myattribute": true + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::MapField AttributeMap { + get { return attributeMap_; } + } + + /// Field number for the "dropped_attributes_count" field. + public const int DroppedAttributesCountFieldNumber = 2; + private int droppedAttributesCount_; + /// + /// The number of attributes that were discarded. Attributes can be discarded + /// because their keys are too long or because there are too many attributes. + /// If this value is 0, then no attributes were dropped. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int DroppedAttributesCount { + get { return droppedAttributesCount_; } + set { + droppedAttributesCount_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Attributes); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Attributes other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!AttributeMap.Equals(other.AttributeMap)) return false; + if (DroppedAttributesCount != other.DroppedAttributesCount) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= AttributeMap.GetHashCode(); + if (DroppedAttributesCount != 0) hash ^= DroppedAttributesCount.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + attributeMap_.WriteTo(output, _map_attributeMap_codec); + if (DroppedAttributesCount != 0) { + output.WriteRawTag(16); + output.WriteInt32(DroppedAttributesCount); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += attributeMap_.CalculateSize(_map_attributeMap_codec); + if (DroppedAttributesCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(DroppedAttributesCount); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Attributes other) { + if (other == null) { + return; + } + attributeMap_.Add(other.attributeMap_); + if (other.DroppedAttributesCount != 0) { + DroppedAttributesCount = other.DroppedAttributesCount; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + attributeMap_.AddEntriesFrom(input, _map_attributeMap_codec); + break; + } + case 16: { + DroppedAttributesCount = input.ReadInt32(); + break; + } + } + } + } + + } + + /// + /// A time-stamped annotation or message event in the Span. + /// + public sealed partial class TimeEvent : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new TimeEvent()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Descriptor.NestedTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TimeEvent() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TimeEvent(TimeEvent other) : this() { + time_ = other.time_ != null ? other.time_.Clone() : null; + switch (other.ValueCase) { + case ValueOneofCase.Annotation: + Annotation = other.Annotation.Clone(); + break; + case ValueOneofCase.MessageEvent: + MessageEvent = other.MessageEvent.Clone(); + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TimeEvent Clone() { + return new TimeEvent(this); + } + + /// Field number for the "time" field. + public const int TimeFieldNumber = 1; + private global::Google.Protobuf.WellKnownTypes.Timestamp time_; + /// + /// The time the event occurred. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Google.Protobuf.WellKnownTypes.Timestamp Time { + get { return time_; } + set { + time_ = value; + } + } + + /// Field number for the "annotation" field. + public const int AnnotationFieldNumber = 2; + /// + /// A text annotation with a set of attributes. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.Annotation Annotation { + get { return valueCase_ == ValueOneofCase.Annotation ? (global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.Annotation) value_ : null; } + set { + value_ = value; + valueCase_ = value == null ? ValueOneofCase.None : ValueOneofCase.Annotation; + } + } + + /// Field number for the "message_event" field. + public const int MessageEventFieldNumber = 3; + /// + /// An event describing a message sent/received between Spans. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent MessageEvent { + get { return valueCase_ == ValueOneofCase.MessageEvent ? (global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent) value_ : null; } + set { + value_ = value; + valueCase_ = value == null ? ValueOneofCase.None : ValueOneofCase.MessageEvent; + } + } + + private object value_; + /// Enum of possible cases for the "value" oneof. + public enum ValueOneofCase { + None = 0, + Annotation = 2, + MessageEvent = 3, + } + private ValueOneofCase valueCase_ = ValueOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ValueOneofCase ValueCase { + get { return valueCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearValue() { + valueCase_ = ValueOneofCase.None; + value_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as TimeEvent); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(TimeEvent other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Time, other.Time)) return false; + if (!object.Equals(Annotation, other.Annotation)) return false; + if (!object.Equals(MessageEvent, other.MessageEvent)) return false; + if (ValueCase != other.ValueCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (time_ != null) hash ^= Time.GetHashCode(); + if (valueCase_ == ValueOneofCase.Annotation) hash ^= Annotation.GetHashCode(); + if (valueCase_ == ValueOneofCase.MessageEvent) hash ^= MessageEvent.GetHashCode(); + hash ^= (int) valueCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (time_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Time); + } + if (valueCase_ == ValueOneofCase.Annotation) { + output.WriteRawTag(18); + output.WriteMessage(Annotation); + } + if (valueCase_ == ValueOneofCase.MessageEvent) { + output.WriteRawTag(26); + output.WriteMessage(MessageEvent); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (time_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Time); + } + if (valueCase_ == ValueOneofCase.Annotation) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Annotation); + } + if (valueCase_ == ValueOneofCase.MessageEvent) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(MessageEvent); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(TimeEvent other) { + if (other == null) { + return; + } + if (other.time_ != null) { + if (time_ == null) { + time_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + Time.MergeFrom(other.Time); + } + switch (other.ValueCase) { + case ValueOneofCase.Annotation: + if (Annotation == null) { + Annotation = new global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.Annotation(); + } + Annotation.MergeFrom(other.Annotation); + break; + case ValueOneofCase.MessageEvent: + if (MessageEvent == null) { + MessageEvent = new global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent(); + } + MessageEvent.MergeFrom(other.MessageEvent); + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (time_ == null) { + time_ = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + input.ReadMessage(time_); + break; + } + case 18: { + global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.Annotation subBuilder = new global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.Annotation(); + if (valueCase_ == ValueOneofCase.Annotation) { + subBuilder.MergeFrom(Annotation); + } + input.ReadMessage(subBuilder); + Annotation = subBuilder; + break; + } + case 26: { + global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent subBuilder = new global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent(); + if (valueCase_ == ValueOneofCase.MessageEvent) { + subBuilder.MergeFrom(MessageEvent); + } + input.ReadMessage(subBuilder); + MessageEvent = subBuilder; + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the TimeEvent message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + /// + /// A text annotation with a set of attributes. + /// + public sealed partial class Annotation : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Annotation()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Descriptor.NestedTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Annotation() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Annotation(Annotation other) : this() { + description_ = other.description_ != null ? other.description_.Clone() : null; + attributes_ = other.attributes_ != null ? other.attributes_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Annotation Clone() { + return new Annotation(this); + } + + /// Field number for the "description" field. + public const int DescriptionFieldNumber = 1; + private global::Opencensus.Proto.Trace.V1.TruncatableString description_; + /// + /// A user-supplied message describing the event. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString Description { + get { return description_; } + set { + description_ = value; + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 2; + private global::Opencensus.Proto.Trace.V1.Span.Types.Attributes attributes_; + /// + /// A set of attributes on the annotation. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.Attributes Attributes { + get { return attributes_; } + set { + attributes_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Annotation); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Annotation other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Description, other.Description)) return false; + if (!object.Equals(Attributes, other.Attributes)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (description_ != null) hash ^= Description.GetHashCode(); + if (attributes_ != null) hash ^= Attributes.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (description_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Description); + } + if (attributes_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Attributes); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (description_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Description); + } + if (attributes_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Attributes); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Annotation other) { + if (other == null) { + return; + } + if (other.description_ != null) { + if (description_ == null) { + description_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + Description.MergeFrom(other.Description); + } + if (other.attributes_ != null) { + if (attributes_ == null) { + attributes_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Attributes(); + } + Attributes.MergeFrom(other.Attributes); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (description_ == null) { + description_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(description_); + break; + } + case 18: { + if (attributes_ == null) { + attributes_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Attributes(); + } + input.ReadMessage(attributes_); + break; + } + } + } + } + + } + + /// + /// An event describing a message sent/received between Spans. + /// + public sealed partial class MessageEvent : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new MessageEvent()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Descriptor.NestedTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public MessageEvent() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public MessageEvent(MessageEvent other) : this() { + type_ = other.type_; + id_ = other.id_; + uncompressedSize_ = other.uncompressedSize_; + compressedSize_ = other.compressedSize_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public MessageEvent Clone() { + return new MessageEvent(this); + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 1; + private global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent.Types.Type type_ = 0; + /// + /// The type of MessageEvent. Indicates whether the message was sent or + /// received. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent.Types.Type Type { + get { return type_; } + set { + type_ = value; + } + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 2; + private ulong id_; + /// + /// An identifier for the MessageEvent's message that can be used to match + /// SENT and RECEIVED MessageEvents. For example, this field could + /// represent a sequence ID for a streaming RPC. It is recommended to be + /// unique within a Span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ulong Id { + get { return id_; } + set { + id_ = value; + } + } + + /// Field number for the "uncompressed_size" field. + public const int UncompressedSizeFieldNumber = 3; + private ulong uncompressedSize_; + /// + /// The number of uncompressed bytes sent or received. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ulong UncompressedSize { + get { return uncompressedSize_; } + set { + uncompressedSize_ = value; + } + } + + /// Field number for the "compressed_size" field. + public const int CompressedSizeFieldNumber = 4; + private ulong compressedSize_; + /// + /// The number of compressed bytes sent or received. If zero, assumed to + /// be the same size as uncompressed. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ulong CompressedSize { + get { return compressedSize_; } + set { + compressedSize_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as MessageEvent); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(MessageEvent other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Type != other.Type) return false; + if (Id != other.Id) return false; + if (UncompressedSize != other.UncompressedSize) return false; + if (CompressedSize != other.CompressedSize) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Type != 0) hash ^= Type.GetHashCode(); + if (Id != 0UL) hash ^= Id.GetHashCode(); + if (UncompressedSize != 0UL) hash ^= UncompressedSize.GetHashCode(); + if (CompressedSize != 0UL) hash ^= CompressedSize.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Type != 0) { + output.WriteRawTag(8); + output.WriteEnum((int) Type); + } + if (Id != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(Id); + } + if (UncompressedSize != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(UncompressedSize); + } + if (CompressedSize != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(CompressedSize); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Type != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Type); + } + if (Id != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Id); + } + if (UncompressedSize != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(UncompressedSize); + } + if (CompressedSize != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(CompressedSize); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(MessageEvent other) { + if (other == null) { + return; + } + if (other.Type != 0) { + Type = other.Type; + } + if (other.Id != 0UL) { + Id = other.Id; + } + if (other.UncompressedSize != 0UL) { + UncompressedSize = other.UncompressedSize; + } + if (other.CompressedSize != 0UL) { + CompressedSize = other.CompressedSize; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + type_ = (global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Types.MessageEvent.Types.Type) input.ReadEnum(); + break; + } + case 16: { + Id = input.ReadUInt64(); + break; + } + case 24: { + UncompressedSize = input.ReadUInt64(); + break; + } + case 32: { + CompressedSize = input.ReadUInt64(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the MessageEvent message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + /// + /// Indicates whether the message was sent or received. + /// + public enum Type { + /// + /// Unknown event type. + /// + [pbr::OriginalName("TYPE_UNSPECIFIED")] Unspecified = 0, + /// + /// Indicates a sent message. + /// + [pbr::OriginalName("SENT")] Sent = 1, + /// + /// Indicates a received message. + /// + [pbr::OriginalName("RECEIVED")] Received = 2, + } + + } + #endregion + + } + + } + #endregion + + } + + /// + /// A collection of `TimeEvent`s. A `TimeEvent` is a time-stamped annotation + /// on the span, consisting of either user-supplied key-value pairs, or + /// details of a message sent/received between Spans. + /// + public sealed partial class TimeEvents : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new TimeEvents()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Descriptor.NestedTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TimeEvents() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TimeEvents(TimeEvents other) : this() { + timeEvent_ = other.timeEvent_.Clone(); + droppedAnnotationsCount_ = other.droppedAnnotationsCount_; + droppedMessageEventsCount_ = other.droppedMessageEventsCount_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TimeEvents Clone() { + return new TimeEvents(this); + } + + /// Field number for the "time_event" field. + public const int TimeEventFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_timeEvent_codec + = pb::FieldCodec.ForMessage(10, global::Opencensus.Proto.Trace.V1.Span.Types.TimeEvent.Parser); + private readonly pbc::RepeatedField timeEvent_ = new pbc::RepeatedField(); + /// + /// A collection of `TimeEvent`s. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField TimeEvent { + get { return timeEvent_; } + } + + /// Field number for the "dropped_annotations_count" field. + public const int DroppedAnnotationsCountFieldNumber = 2; + private int droppedAnnotationsCount_; + /// + /// The number of dropped annotations in all the included time events. + /// If the value is 0, then no annotations were dropped. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int DroppedAnnotationsCount { + get { return droppedAnnotationsCount_; } + set { + droppedAnnotationsCount_ = value; + } + } + + /// Field number for the "dropped_message_events_count" field. + public const int DroppedMessageEventsCountFieldNumber = 3; + private int droppedMessageEventsCount_; + /// + /// The number of dropped message events in all the included time events. + /// If the value is 0, then no message events were dropped. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int DroppedMessageEventsCount { + get { return droppedMessageEventsCount_; } + set { + droppedMessageEventsCount_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as TimeEvents); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(TimeEvents other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!timeEvent_.Equals(other.timeEvent_)) return false; + if (DroppedAnnotationsCount != other.DroppedAnnotationsCount) return false; + if (DroppedMessageEventsCount != other.DroppedMessageEventsCount) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= timeEvent_.GetHashCode(); + if (DroppedAnnotationsCount != 0) hash ^= DroppedAnnotationsCount.GetHashCode(); + if (DroppedMessageEventsCount != 0) hash ^= DroppedMessageEventsCount.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + timeEvent_.WriteTo(output, _repeated_timeEvent_codec); + if (DroppedAnnotationsCount != 0) { + output.WriteRawTag(16); + output.WriteInt32(DroppedAnnotationsCount); + } + if (DroppedMessageEventsCount != 0) { + output.WriteRawTag(24); + output.WriteInt32(DroppedMessageEventsCount); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += timeEvent_.CalculateSize(_repeated_timeEvent_codec); + if (DroppedAnnotationsCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(DroppedAnnotationsCount); + } + if (DroppedMessageEventsCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(DroppedMessageEventsCount); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(TimeEvents other) { + if (other == null) { + return; + } + timeEvent_.Add(other.timeEvent_); + if (other.DroppedAnnotationsCount != 0) { + DroppedAnnotationsCount = other.DroppedAnnotationsCount; + } + if (other.DroppedMessageEventsCount != 0) { + DroppedMessageEventsCount = other.DroppedMessageEventsCount; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + timeEvent_.AddEntriesFrom(input, _repeated_timeEvent_codec); + break; + } + case 16: { + DroppedAnnotationsCount = input.ReadInt32(); + break; + } + case 24: { + DroppedMessageEventsCount = input.ReadInt32(); + break; + } + } + } + } + + } + + /// + /// A pointer from the current span to another span in the same trace or in a + /// different trace. For example, this can be used in batching operations, + /// where a single batch handler processes multiple requests from different + /// traces or when the handler receives a request from a different project. + /// + public sealed partial class Link : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Link()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Descriptor.NestedTypes[4]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Link() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Link(Link other) : this() { + traceId_ = other.traceId_; + spanId_ = other.spanId_; + type_ = other.type_; + attributes_ = other.attributes_ != null ? other.attributes_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Link Clone() { + return new Link(this); + } + + /// Field number for the "trace_id" field. + public const int TraceIdFieldNumber = 1; + private pb::ByteString traceId_ = pb::ByteString.Empty; + /// + /// A unique identifier for a trace. All spans from the same trace share + /// the same `trace_id`. The ID is a 16-byte array. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString TraceId { + get { return traceId_; } + set { + traceId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "span_id" field. + public const int SpanIdFieldNumber = 2; + private pb::ByteString spanId_ = pb::ByteString.Empty; + /// + /// A unique identifier for a span within a trace, assigned when the span + /// is created. The ID is an 8-byte array. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString SpanId { + get { return spanId_; } + set { + spanId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 3; + private global::Opencensus.Proto.Trace.V1.Span.Types.Link.Types.Type type_ = 0; + /// + /// The relationship of the current span relative to the linked span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.Link.Types.Type Type { + get { return type_; } + set { + type_ = value; + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 4; + private global::Opencensus.Proto.Trace.V1.Span.Types.Attributes attributes_; + /// + /// A set of attributes on the link. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Span.Types.Attributes Attributes { + get { return attributes_; } + set { + attributes_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Link); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Link other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (TraceId != other.TraceId) return false; + if (SpanId != other.SpanId) return false; + if (Type != other.Type) return false; + if (!object.Equals(Attributes, other.Attributes)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (TraceId.Length != 0) hash ^= TraceId.GetHashCode(); + if (SpanId.Length != 0) hash ^= SpanId.GetHashCode(); + if (Type != 0) hash ^= Type.GetHashCode(); + if (attributes_ != null) hash ^= Attributes.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (TraceId.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(TraceId); + } + if (SpanId.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(SpanId); + } + if (Type != 0) { + output.WriteRawTag(24); + output.WriteEnum((int) Type); + } + if (attributes_ != null) { + output.WriteRawTag(34); + output.WriteMessage(Attributes); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (TraceId.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(TraceId); + } + if (SpanId.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SpanId); + } + if (Type != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Type); + } + if (attributes_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Attributes); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Link other) { + if (other == null) { + return; + } + if (other.TraceId.Length != 0) { + TraceId = other.TraceId; + } + if (other.SpanId.Length != 0) { + SpanId = other.SpanId; + } + if (other.Type != 0) { + Type = other.Type; + } + if (other.attributes_ != null) { + if (attributes_ == null) { + attributes_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Attributes(); + } + Attributes.MergeFrom(other.Attributes); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + TraceId = input.ReadBytes(); + break; + } + case 18: { + SpanId = input.ReadBytes(); + break; + } + case 24: { + type_ = (global::Opencensus.Proto.Trace.V1.Span.Types.Link.Types.Type) input.ReadEnum(); + break; + } + case 34: { + if (attributes_ == null) { + attributes_ = new global::Opencensus.Proto.Trace.V1.Span.Types.Attributes(); + } + input.ReadMessage(attributes_); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the Link message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + /// + /// The relationship of the current span relative to the linked span: child, + /// parent, or unspecified. + /// + public enum Type { + /// + /// The relationship of the two spans is unknown, or known but other + /// than parent-child. + /// + [pbr::OriginalName("TYPE_UNSPECIFIED")] Unspecified = 0, + /// + /// The linked span is a child of the current span. + /// + [pbr::OriginalName("CHILD_LINKED_SPAN")] ChildLinkedSpan = 1, + /// + /// The linked span is a parent of the current span. + /// + [pbr::OriginalName("PARENT_LINKED_SPAN")] ParentLinkedSpan = 2, + } + + } + #endregion + + } + + /// + /// A collection of links, which are references from this span to a span + /// in the same or different trace. + /// + public sealed partial class Links : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Links()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.Span.Descriptor.NestedTypes[5]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Links() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Links(Links other) : this() { + link_ = other.link_.Clone(); + droppedLinksCount_ = other.droppedLinksCount_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Links Clone() { + return new Links(this); + } + + /// Field number for the "link" field. + public const int LinkFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_link_codec + = pb::FieldCodec.ForMessage(10, global::Opencensus.Proto.Trace.V1.Span.Types.Link.Parser); + private readonly pbc::RepeatedField link_ = new pbc::RepeatedField(); + /// + /// A collection of links. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Link { + get { return link_; } + } + + /// Field number for the "dropped_links_count" field. + public const int DroppedLinksCountFieldNumber = 2; + private int droppedLinksCount_; + /// + /// The number of dropped links after the maximum size was enforced. If + /// this value is 0, then no links were dropped. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int DroppedLinksCount { + get { return droppedLinksCount_; } + set { + droppedLinksCount_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Links); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Links other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!link_.Equals(other.link_)) return false; + if (DroppedLinksCount != other.DroppedLinksCount) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= link_.GetHashCode(); + if (DroppedLinksCount != 0) hash ^= DroppedLinksCount.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + link_.WriteTo(output, _repeated_link_codec); + if (DroppedLinksCount != 0) { + output.WriteRawTag(16); + output.WriteInt32(DroppedLinksCount); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += link_.CalculateSize(_repeated_link_codec); + if (DroppedLinksCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(DroppedLinksCount); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Links other) { + if (other == null) { + return; + } + link_.Add(other.link_); + if (other.DroppedLinksCount != 0) { + DroppedLinksCount = other.DroppedLinksCount; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + link_.AddEntriesFrom(input, _repeated_link_codec); + break; + } + case 16: { + DroppedLinksCount = input.ReadInt32(); + break; + } + } + } + } + + } + + } + #endregion + + } + + /// + /// The `Status` type defines a logical error model that is suitable for different + /// programming environments, including REST APIs and RPC APIs. This proto's fields + /// are a subset of those of + /// [google.rpc.Status](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto), + /// which is used by [gRPC](https://github.com/grpc). + /// + public sealed partial class Status : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Status()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Status() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Status(Status other) : this() { + code_ = other.code_; + message_ = other.message_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Status Clone() { + return new Status(this); + } + + /// Field number for the "code" field. + public const int CodeFieldNumber = 1; + private int code_; + /// + /// The status code. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int Code { + get { return code_; } + set { + code_ = value; + } + } + + /// Field number for the "message" field. + public const int MessageFieldNumber = 2; + private string message_ = ""; + /// + /// A developer-facing error message, which should be in English. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Message { + get { return message_; } + set { + message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Status); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Status other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Code != other.Code) return false; + if (Message != other.Message) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Code != 0) hash ^= Code.GetHashCode(); + if (Message.Length != 0) hash ^= Message.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Code != 0) { + output.WriteRawTag(8); + output.WriteInt32(Code); + } + if (Message.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Message); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Code != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(Code); + } + if (Message.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Message); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Status other) { + if (other == null) { + return; + } + if (other.Code != 0) { + Code = other.Code; + } + if (other.Message.Length != 0) { + Message = other.Message; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Code = input.ReadInt32(); + break; + } + case 18: { + Message = input.ReadString(); + break; + } + } + } + } + + } + + /// + /// The value of an Attribute. + /// + public sealed partial class AttributeValue : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new AttributeValue()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceReflection.Descriptor.MessageTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AttributeValue() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AttributeValue(AttributeValue other) : this() { + switch (other.ValueCase) { + case ValueOneofCase.StringValue: + StringValue = other.StringValue.Clone(); + break; + case ValueOneofCase.IntValue: + IntValue = other.IntValue; + break; + case ValueOneofCase.BoolValue: + BoolValue = other.BoolValue; + break; + case ValueOneofCase.DoubleValue: + DoubleValue = other.DoubleValue; + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public AttributeValue Clone() { + return new AttributeValue(this); + } + + /// Field number for the "string_value" field. + public const int StringValueFieldNumber = 1; + /// + /// A string up to 256 bytes long. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString StringValue { + get { return valueCase_ == ValueOneofCase.StringValue ? (global::Opencensus.Proto.Trace.V1.TruncatableString) value_ : null; } + set { + value_ = value; + valueCase_ = value == null ? ValueOneofCase.None : ValueOneofCase.StringValue; + } + } + + /// Field number for the "int_value" field. + public const int IntValueFieldNumber = 2; + /// + /// A 64-bit signed integer. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long IntValue { + get { return valueCase_ == ValueOneofCase.IntValue ? (long) value_ : 0L; } + set { + value_ = value; + valueCase_ = ValueOneofCase.IntValue; + } + } + + /// Field number for the "bool_value" field. + public const int BoolValueFieldNumber = 3; + /// + /// A Boolean value represented by `true` or `false`. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool BoolValue { + get { return valueCase_ == ValueOneofCase.BoolValue ? (bool) value_ : false; } + set { + value_ = value; + valueCase_ = ValueOneofCase.BoolValue; + } + } + + /// Field number for the "double_value" field. + public const int DoubleValueFieldNumber = 4; + /// + /// A double value. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public double DoubleValue { + get { return valueCase_ == ValueOneofCase.DoubleValue ? (double) value_ : 0D; } + set { + value_ = value; + valueCase_ = ValueOneofCase.DoubleValue; + } + } + + private object value_; + /// Enum of possible cases for the "value" oneof. + public enum ValueOneofCase { + None = 0, + StringValue = 1, + IntValue = 2, + BoolValue = 3, + DoubleValue = 4, + } + private ValueOneofCase valueCase_ = ValueOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ValueOneofCase ValueCase { + get { return valueCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearValue() { + valueCase_ = ValueOneofCase.None; + value_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as AttributeValue); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(AttributeValue other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(StringValue, other.StringValue)) return false; + if (IntValue != other.IntValue) return false; + if (BoolValue != other.BoolValue) return false; + if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(DoubleValue, other.DoubleValue)) return false; + if (ValueCase != other.ValueCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (valueCase_ == ValueOneofCase.StringValue) hash ^= StringValue.GetHashCode(); + if (valueCase_ == ValueOneofCase.IntValue) hash ^= IntValue.GetHashCode(); + if (valueCase_ == ValueOneofCase.BoolValue) hash ^= BoolValue.GetHashCode(); + if (valueCase_ == ValueOneofCase.DoubleValue) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(DoubleValue); + hash ^= (int) valueCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (valueCase_ == ValueOneofCase.StringValue) { + output.WriteRawTag(10); + output.WriteMessage(StringValue); + } + if (valueCase_ == ValueOneofCase.IntValue) { + output.WriteRawTag(16); + output.WriteInt64(IntValue); + } + if (valueCase_ == ValueOneofCase.BoolValue) { + output.WriteRawTag(24); + output.WriteBool(BoolValue); + } + if (valueCase_ == ValueOneofCase.DoubleValue) { + output.WriteRawTag(33); + output.WriteDouble(DoubleValue); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (valueCase_ == ValueOneofCase.StringValue) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(StringValue); + } + if (valueCase_ == ValueOneofCase.IntValue) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(IntValue); + } + if (valueCase_ == ValueOneofCase.BoolValue) { + size += 1 + 1; + } + if (valueCase_ == ValueOneofCase.DoubleValue) { + size += 1 + 8; + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(AttributeValue other) { + if (other == null) { + return; + } + switch (other.ValueCase) { + case ValueOneofCase.StringValue: + if (StringValue == null) { + StringValue = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + StringValue.MergeFrom(other.StringValue); + break; + case ValueOneofCase.IntValue: + IntValue = other.IntValue; + break; + case ValueOneofCase.BoolValue: + BoolValue = other.BoolValue; + break; + case ValueOneofCase.DoubleValue: + DoubleValue = other.DoubleValue; + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + global::Opencensus.Proto.Trace.V1.TruncatableString subBuilder = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + if (valueCase_ == ValueOneofCase.StringValue) { + subBuilder.MergeFrom(StringValue); + } + input.ReadMessage(subBuilder); + StringValue = subBuilder; + break; + } + case 16: { + IntValue = input.ReadInt64(); + break; + } + case 24: { + BoolValue = input.ReadBool(); + break; + } + case 33: { + DoubleValue = input.ReadDouble(); + break; + } + } + } + } + + } + + /// + /// The call stack which originated this span. + /// + public sealed partial class StackTrace : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new StackTrace()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceReflection.Descriptor.MessageTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackTrace() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackTrace(StackTrace other) : this() { + stackFrames_ = other.stackFrames_ != null ? other.stackFrames_.Clone() : null; + stackTraceHashId_ = other.stackTraceHashId_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackTrace Clone() { + return new StackTrace(this); + } + + /// Field number for the "stack_frames" field. + public const int StackFramesFieldNumber = 1; + private global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrames stackFrames_; + /// + /// Stack frames in this stack trace. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrames StackFrames { + get { return stackFrames_; } + set { + stackFrames_ = value; + } + } + + /// Field number for the "stack_trace_hash_id" field. + public const int StackTraceHashIdFieldNumber = 2; + private ulong stackTraceHashId_; + /// + /// The hash ID is used to conserve network bandwidth for duplicate + /// stack traces within a single trace. + /// + /// Often multiple spans will have identical stack traces. + /// The first occurrence of a stack trace should contain both + /// `stack_frames` and a value in `stack_trace_hash_id`. + /// + /// Subsequent spans within the same request can refer + /// to that stack trace by setting only `stack_trace_hash_id`. + /// + /// TODO: describe how to deal with the case where stack_trace_hash_id is + /// zero because it was not set. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ulong StackTraceHashId { + get { return stackTraceHashId_; } + set { + stackTraceHashId_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as StackTrace); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(StackTrace other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(StackFrames, other.StackFrames)) return false; + if (StackTraceHashId != other.StackTraceHashId) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (stackFrames_ != null) hash ^= StackFrames.GetHashCode(); + if (StackTraceHashId != 0UL) hash ^= StackTraceHashId.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (stackFrames_ != null) { + output.WriteRawTag(10); + output.WriteMessage(StackFrames); + } + if (StackTraceHashId != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(StackTraceHashId); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (stackFrames_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(StackFrames); + } + if (StackTraceHashId != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(StackTraceHashId); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(StackTrace other) { + if (other == null) { + return; + } + if (other.stackFrames_ != null) { + if (stackFrames_ == null) { + stackFrames_ = new global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrames(); + } + StackFrames.MergeFrom(other.StackFrames); + } + if (other.StackTraceHashId != 0UL) { + StackTraceHashId = other.StackTraceHashId; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (stackFrames_ == null) { + stackFrames_ = new global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrames(); + } + input.ReadMessage(stackFrames_); + break; + } + case 16: { + StackTraceHashId = input.ReadUInt64(); + break; + } + } + } + } + + #region Nested types + /// Container for nested types declared in the StackTrace message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + /// + /// A single stack frame in a stack trace. + /// + public sealed partial class StackFrame : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new StackFrame()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.StackTrace.Descriptor.NestedTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackFrame() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackFrame(StackFrame other) : this() { + functionName_ = other.functionName_ != null ? other.functionName_.Clone() : null; + originalFunctionName_ = other.originalFunctionName_ != null ? other.originalFunctionName_.Clone() : null; + fileName_ = other.fileName_ != null ? other.fileName_.Clone() : null; + lineNumber_ = other.lineNumber_; + columnNumber_ = other.columnNumber_; + loadModule_ = other.loadModule_ != null ? other.loadModule_.Clone() : null; + sourceVersion_ = other.sourceVersion_ != null ? other.sourceVersion_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackFrame Clone() { + return new StackFrame(this); + } + + /// Field number for the "function_name" field. + public const int FunctionNameFieldNumber = 1; + private global::Opencensus.Proto.Trace.V1.TruncatableString functionName_; + /// + /// The fully-qualified name that uniquely identifies the function or + /// method that is active in this frame. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString FunctionName { + get { return functionName_; } + set { + functionName_ = value; + } + } + + /// Field number for the "original_function_name" field. + public const int OriginalFunctionNameFieldNumber = 2; + private global::Opencensus.Proto.Trace.V1.TruncatableString originalFunctionName_; + /// + /// An un-mangled function name, if `function_name` is + /// [mangled](http://www.avabodh.com/cxxin/namemangling.html). The name can + /// be fully qualified. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString OriginalFunctionName { + get { return originalFunctionName_; } + set { + originalFunctionName_ = value; + } + } + + /// Field number for the "file_name" field. + public const int FileNameFieldNumber = 3; + private global::Opencensus.Proto.Trace.V1.TruncatableString fileName_; + /// + /// The name of the source file where the function call appears. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString FileName { + get { return fileName_; } + set { + fileName_ = value; + } + } + + /// Field number for the "line_number" field. + public const int LineNumberFieldNumber = 4; + private long lineNumber_; + /// + /// The line number in `file_name` where the function call appears. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long LineNumber { + get { return lineNumber_; } + set { + lineNumber_ = value; + } + } + + /// Field number for the "column_number" field. + public const int ColumnNumberFieldNumber = 5; + private long columnNumber_; + /// + /// The column number where the function call appears, if available. + /// This is important in JavaScript because of its anonymous functions. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long ColumnNumber { + get { return columnNumber_; } + set { + columnNumber_ = value; + } + } + + /// Field number for the "load_module" field. + public const int LoadModuleFieldNumber = 6; + private global::Opencensus.Proto.Trace.V1.Module loadModule_; + /// + /// The binary module from where the code was loaded. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.Module LoadModule { + get { return loadModule_; } + set { + loadModule_ = value; + } + } + + /// Field number for the "source_version" field. + public const int SourceVersionFieldNumber = 7; + private global::Opencensus.Proto.Trace.V1.TruncatableString sourceVersion_; + /// + /// The version of the deployed source code. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString SourceVersion { + get { return sourceVersion_; } + set { + sourceVersion_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as StackFrame); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(StackFrame other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(FunctionName, other.FunctionName)) return false; + if (!object.Equals(OriginalFunctionName, other.OriginalFunctionName)) return false; + if (!object.Equals(FileName, other.FileName)) return false; + if (LineNumber != other.LineNumber) return false; + if (ColumnNumber != other.ColumnNumber) return false; + if (!object.Equals(LoadModule, other.LoadModule)) return false; + if (!object.Equals(SourceVersion, other.SourceVersion)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (functionName_ != null) hash ^= FunctionName.GetHashCode(); + if (originalFunctionName_ != null) hash ^= OriginalFunctionName.GetHashCode(); + if (fileName_ != null) hash ^= FileName.GetHashCode(); + if (LineNumber != 0L) hash ^= LineNumber.GetHashCode(); + if (ColumnNumber != 0L) hash ^= ColumnNumber.GetHashCode(); + if (loadModule_ != null) hash ^= LoadModule.GetHashCode(); + if (sourceVersion_ != null) hash ^= SourceVersion.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (functionName_ != null) { + output.WriteRawTag(10); + output.WriteMessage(FunctionName); + } + if (originalFunctionName_ != null) { + output.WriteRawTag(18); + output.WriteMessage(OriginalFunctionName); + } + if (fileName_ != null) { + output.WriteRawTag(26); + output.WriteMessage(FileName); + } + if (LineNumber != 0L) { + output.WriteRawTag(32); + output.WriteInt64(LineNumber); + } + if (ColumnNumber != 0L) { + output.WriteRawTag(40); + output.WriteInt64(ColumnNumber); + } + if (loadModule_ != null) { + output.WriteRawTag(50); + output.WriteMessage(LoadModule); + } + if (sourceVersion_ != null) { + output.WriteRawTag(58); + output.WriteMessage(SourceVersion); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (functionName_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(FunctionName); + } + if (originalFunctionName_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(OriginalFunctionName); + } + if (fileName_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(FileName); + } + if (LineNumber != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(LineNumber); + } + if (ColumnNumber != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(ColumnNumber); + } + if (loadModule_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(LoadModule); + } + if (sourceVersion_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(SourceVersion); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(StackFrame other) { + if (other == null) { + return; + } + if (other.functionName_ != null) { + if (functionName_ == null) { + functionName_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + FunctionName.MergeFrom(other.FunctionName); + } + if (other.originalFunctionName_ != null) { + if (originalFunctionName_ == null) { + originalFunctionName_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + OriginalFunctionName.MergeFrom(other.OriginalFunctionName); + } + if (other.fileName_ != null) { + if (fileName_ == null) { + fileName_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + FileName.MergeFrom(other.FileName); + } + if (other.LineNumber != 0L) { + LineNumber = other.LineNumber; + } + if (other.ColumnNumber != 0L) { + ColumnNumber = other.ColumnNumber; + } + if (other.loadModule_ != null) { + if (loadModule_ == null) { + loadModule_ = new global::Opencensus.Proto.Trace.V1.Module(); + } + LoadModule.MergeFrom(other.LoadModule); + } + if (other.sourceVersion_ != null) { + if (sourceVersion_ == null) { + sourceVersion_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + SourceVersion.MergeFrom(other.SourceVersion); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (functionName_ == null) { + functionName_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(functionName_); + break; + } + case 18: { + if (originalFunctionName_ == null) { + originalFunctionName_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(originalFunctionName_); + break; + } + case 26: { + if (fileName_ == null) { + fileName_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(fileName_); + break; + } + case 32: { + LineNumber = input.ReadInt64(); + break; + } + case 40: { + ColumnNumber = input.ReadInt64(); + break; + } + case 50: { + if (loadModule_ == null) { + loadModule_ = new global::Opencensus.Proto.Trace.V1.Module(); + } + input.ReadMessage(loadModule_); + break; + } + case 58: { + if (sourceVersion_ == null) { + sourceVersion_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(sourceVersion_); + break; + } + } + } + } + + } + + /// + /// A collection of stack frames, which can be truncated. + /// + public sealed partial class StackFrames : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new StackFrames()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.StackTrace.Descriptor.NestedTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackFrames() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackFrames(StackFrames other) : this() { + frame_ = other.frame_.Clone(); + droppedFramesCount_ = other.droppedFramesCount_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public StackFrames Clone() { + return new StackFrames(this); + } + + /// Field number for the "frame" field. + public const int FrameFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_frame_codec + = pb::FieldCodec.ForMessage(10, global::Opencensus.Proto.Trace.V1.StackTrace.Types.StackFrame.Parser); + private readonly pbc::RepeatedField frame_ = new pbc::RepeatedField(); + /// + /// Stack frames in this call stack. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Frame { + get { return frame_; } + } + + /// Field number for the "dropped_frames_count" field. + public const int DroppedFramesCountFieldNumber = 2; + private int droppedFramesCount_; + /// + /// The number of stack frames that were dropped because there + /// were too many stack frames. + /// If this value is 0, then no stack frames were dropped. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int DroppedFramesCount { + get { return droppedFramesCount_; } + set { + droppedFramesCount_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as StackFrames); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(StackFrames other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!frame_.Equals(other.frame_)) return false; + if (DroppedFramesCount != other.DroppedFramesCount) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + hash ^= frame_.GetHashCode(); + if (DroppedFramesCount != 0) hash ^= DroppedFramesCount.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + frame_.WriteTo(output, _repeated_frame_codec); + if (DroppedFramesCount != 0) { + output.WriteRawTag(16); + output.WriteInt32(DroppedFramesCount); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + size += frame_.CalculateSize(_repeated_frame_codec); + if (DroppedFramesCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(DroppedFramesCount); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(StackFrames other) { + if (other == null) { + return; + } + frame_.Add(other.frame_); + if (other.DroppedFramesCount != 0) { + DroppedFramesCount = other.DroppedFramesCount; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + frame_.AddEntriesFrom(input, _repeated_frame_codec); + break; + } + case 16: { + DroppedFramesCount = input.ReadInt32(); + break; + } + } + } + } + + } + + } + #endregion + + } + + /// + /// A description of a binary module. + /// + public sealed partial class Module : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Module()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceReflection.Descriptor.MessageTypes[4]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Module() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Module(Module other) : this() { + module_ = other.module_ != null ? other.module_.Clone() : null; + buildId_ = other.buildId_ != null ? other.buildId_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Module Clone() { + return new Module(this); + } + + /// Field number for the "module" field. + public const int Module_FieldNumber = 1; + private global::Opencensus.Proto.Trace.V1.TruncatableString module_; + /// + /// TODO: document the meaning of this field. + /// For example: main binary, kernel modules, and dynamic libraries + /// such as libc.so, sharedlib.so. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString Module_ { + get { return module_; } + set { + module_ = value; + } + } + + /// Field number for the "build_id" field. + public const int BuildIdFieldNumber = 2; + private global::Opencensus.Proto.Trace.V1.TruncatableString buildId_; + /// + /// A unique identifier for the module, usually a hash of its + /// contents. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TruncatableString BuildId { + get { return buildId_; } + set { + buildId_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Module); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Module other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Module_, other.Module_)) return false; + if (!object.Equals(BuildId, other.BuildId)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (module_ != null) hash ^= Module_.GetHashCode(); + if (buildId_ != null) hash ^= BuildId.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (module_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Module_); + } + if (buildId_ != null) { + output.WriteRawTag(18); + output.WriteMessage(BuildId); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (module_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Module_); + } + if (buildId_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(BuildId); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Module other) { + if (other == null) { + return; + } + if (other.module_ != null) { + if (module_ == null) { + module_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + Module_.MergeFrom(other.Module_); + } + if (other.buildId_ != null) { + if (buildId_ == null) { + buildId_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + BuildId.MergeFrom(other.BuildId); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (module_ == null) { + module_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(module_); + break; + } + case 18: { + if (buildId_ == null) { + buildId_ = new global::Opencensus.Proto.Trace.V1.TruncatableString(); + } + input.ReadMessage(buildId_); + break; + } + } + } + } + + } + + /// + /// A string that might be shortened to a specified length. + /// + public sealed partial class TruncatableString : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new TruncatableString()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceReflection.Descriptor.MessageTypes[5]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TruncatableString() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TruncatableString(TruncatableString other) : this() { + value_ = other.value_; + truncatedByteCount_ = other.truncatedByteCount_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TruncatableString Clone() { + return new TruncatableString(this); + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 1; + private string value_ = ""; + /// + /// The shortened string. For example, if the original string was 500 bytes long and + /// the limit of the string was 128 bytes, then this value contains the first 128 + /// bytes of the 500-byte string. Note that truncation always happens on a + /// character boundary, to ensure that a truncated string is still valid UTF-8. + /// Because it may contain multi-byte characters, the size of the truncated string + /// may be less than the truncation limit. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Value { + get { return value_; } + set { + value_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "truncated_byte_count" field. + public const int TruncatedByteCountFieldNumber = 2; + private int truncatedByteCount_; + /// + /// The number of bytes removed from the original string. If this + /// value is 0, then the string was not shortened. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int TruncatedByteCount { + get { return truncatedByteCount_; } + set { + truncatedByteCount_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as TruncatableString); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(TruncatableString other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Value != other.Value) return false; + if (TruncatedByteCount != other.TruncatedByteCount) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Value.Length != 0) hash ^= Value.GetHashCode(); + if (TruncatedByteCount != 0) hash ^= TruncatedByteCount.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Value.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Value); + } + if (TruncatedByteCount != 0) { + output.WriteRawTag(16); + output.WriteInt32(TruncatedByteCount); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Value.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Value); + } + if (TruncatedByteCount != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(TruncatedByteCount); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(TruncatableString other) { + if (other == null) { + return; + } + if (other.Value.Length != 0) { + Value = other.Value; + } + if (other.TruncatedByteCount != 0) { + TruncatedByteCount = other.TruncatedByteCount; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Value = input.ReadString(); + break; + } + case 16: { + TruncatedByteCount = input.ReadInt32(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceConfig.g.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceConfig.g.cs new file mode 100644 index 000000000..f2c03d7fc --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceConfig.g.cs @@ -0,0 +1,837 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: trace/v1/trace_config.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Opencensus.Proto.Trace.V1 { + + /// Holder for reflection information generated from trace/v1/trace_config.proto + public static partial class TraceConfigReflection { + + #region Descriptor + /// File descriptor for trace/v1/trace_config.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static TraceConfigReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Cht0cmFjZS92MS90cmFjZV9jb25maWcucHJvdG8SGW9wZW5jZW5zdXMucHJv", + "dG8udHJhY2UudjEihwMKC1RyYWNlQ29uZmlnEkwKE3Byb2JhYmlsaXR5X3Nh", + "bXBsZXIYASABKAsyLS5vcGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLlByb2Jh", + "YmlsaXR5U2FtcGxlckgAEkYKEGNvbnN0YW50X3NhbXBsZXIYAiABKAsyKi5v", + "cGVuY2Vuc3VzLnByb3RvLnRyYWNlLnYxLkNvbnN0YW50U2FtcGxlckgAEk8K", + "FXJhdGVfbGltaXRpbmdfc2FtcGxlchgDIAEoCzIuLm9wZW5jZW5zdXMucHJv", + "dG8udHJhY2UudjEuUmF0ZUxpbWl0aW5nU2FtcGxlckgAEiAKGG1heF9udW1i", + "ZXJfb2ZfYXR0cmlidXRlcxgEIAEoAxIhChltYXhfbnVtYmVyX29mX2Fubm90", + "YXRpb25zGAUgASgDEiQKHG1heF9udW1iZXJfb2ZfbWVzc2FnZV9ldmVudHMY", + "BiABKAMSGwoTbWF4X251bWJlcl9vZl9saW5rcxgHIAEoA0IJCgdzYW1wbGVy", + "IjEKElByb2JhYmlsaXR5U2FtcGxlchIbChNzYW1wbGluZ1Byb2JhYmlsaXR5", + "GAEgASgBIiMKD0NvbnN0YW50U2FtcGxlchIQCghkZWNpc2lvbhgBIAEoCCIi", + "ChNSYXRlTGltaXRpbmdTYW1wbGVyEgsKA3FwcxgBIAEoA0J2Chxpby5vcGVu", + "Y2Vuc3VzLnByb3RvLnRyYWNlLnYxQhBUcmFjZUNvbmZpZ1Byb3RvUAFaQmdp", + "dGh1Yi5jb20vY2Vuc3VzLWluc3RydW1lbnRhdGlvbi9vcGVuY2Vuc3VzLXBy", + "b3RvL2dlbi1nby90cmFjZS92MWIGcHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.TraceConfig), global::Opencensus.Proto.Trace.V1.TraceConfig.Parser, new[]{ "ProbabilitySampler", "ConstantSampler", "RateLimitingSampler", "MaxNumberOfAttributes", "MaxNumberOfAnnotations", "MaxNumberOfMessageEvents", "MaxNumberOfLinks" }, new[]{ "Sampler" }, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.ProbabilitySampler), global::Opencensus.Proto.Trace.V1.ProbabilitySampler.Parser, new[]{ "SamplingProbability" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.ConstantSampler), global::Opencensus.Proto.Trace.V1.ConstantSampler.Parser, new[]{ "Decision" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Trace.V1.RateLimitingSampler), global::Opencensus.Proto.Trace.V1.RateLimitingSampler.Parser, new[]{ "Qps" }, null, null, null) + })); + } + #endregion + + } + #region Messages + /// + /// Global configuration of the trace service. + /// + public sealed partial class TraceConfig : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new TraceConfig()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceConfigReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TraceConfig() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TraceConfig(TraceConfig other) : this() { + maxNumberOfAttributes_ = other.maxNumberOfAttributes_; + maxNumberOfAnnotations_ = other.maxNumberOfAnnotations_; + maxNumberOfMessageEvents_ = other.maxNumberOfMessageEvents_; + maxNumberOfLinks_ = other.maxNumberOfLinks_; + switch (other.SamplerCase) { + case SamplerOneofCase.ProbabilitySampler: + ProbabilitySampler = other.ProbabilitySampler.Clone(); + break; + case SamplerOneofCase.ConstantSampler: + ConstantSampler = other.ConstantSampler.Clone(); + break; + case SamplerOneofCase.RateLimitingSampler: + RateLimitingSampler = other.RateLimitingSampler.Clone(); + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public TraceConfig Clone() { + return new TraceConfig(this); + } + + /// Field number for the "probability_sampler" field. + public const int ProbabilitySamplerFieldNumber = 1; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.ProbabilitySampler ProbabilitySampler { + get { return samplerCase_ == SamplerOneofCase.ProbabilitySampler ? (global::Opencensus.Proto.Trace.V1.ProbabilitySampler) sampler_ : null; } + set { + sampler_ = value; + samplerCase_ = value == null ? SamplerOneofCase.None : SamplerOneofCase.ProbabilitySampler; + } + } + + /// Field number for the "constant_sampler" field. + public const int ConstantSamplerFieldNumber = 2; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.ConstantSampler ConstantSampler { + get { return samplerCase_ == SamplerOneofCase.ConstantSampler ? (global::Opencensus.Proto.Trace.V1.ConstantSampler) sampler_ : null; } + set { + sampler_ = value; + samplerCase_ = value == null ? SamplerOneofCase.None : SamplerOneofCase.ConstantSampler; + } + } + + /// Field number for the "rate_limiting_sampler" field. + public const int RateLimitingSamplerFieldNumber = 3; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.RateLimitingSampler RateLimitingSampler { + get { return samplerCase_ == SamplerOneofCase.RateLimitingSampler ? (global::Opencensus.Proto.Trace.V1.RateLimitingSampler) sampler_ : null; } + set { + sampler_ = value; + samplerCase_ = value == null ? SamplerOneofCase.None : SamplerOneofCase.RateLimitingSampler; + } + } + + /// Field number for the "max_number_of_attributes" field. + public const int MaxNumberOfAttributesFieldNumber = 4; + private long maxNumberOfAttributes_; + /// + /// The global default max number of attributes per span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long MaxNumberOfAttributes { + get { return maxNumberOfAttributes_; } + set { + maxNumberOfAttributes_ = value; + } + } + + /// Field number for the "max_number_of_annotations" field. + public const int MaxNumberOfAnnotationsFieldNumber = 5; + private long maxNumberOfAnnotations_; + /// + /// The global default max number of annotation events per span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long MaxNumberOfAnnotations { + get { return maxNumberOfAnnotations_; } + set { + maxNumberOfAnnotations_ = value; + } + } + + /// Field number for the "max_number_of_message_events" field. + public const int MaxNumberOfMessageEventsFieldNumber = 6; + private long maxNumberOfMessageEvents_; + /// + /// The global default max number of message events per span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long MaxNumberOfMessageEvents { + get { return maxNumberOfMessageEvents_; } + set { + maxNumberOfMessageEvents_ = value; + } + } + + /// Field number for the "max_number_of_links" field. + public const int MaxNumberOfLinksFieldNumber = 7; + private long maxNumberOfLinks_; + /// + /// The global default max number of link entries per span. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long MaxNumberOfLinks { + get { return maxNumberOfLinks_; } + set { + maxNumberOfLinks_ = value; + } + } + + private object sampler_; + /// Enum of possible cases for the "sampler" oneof. + public enum SamplerOneofCase { + None = 0, + ProbabilitySampler = 1, + ConstantSampler = 2, + RateLimitingSampler = 3, + } + private SamplerOneofCase samplerCase_ = SamplerOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SamplerOneofCase SamplerCase { + get { return samplerCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void ClearSampler() { + samplerCase_ = SamplerOneofCase.None; + sampler_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as TraceConfig); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(TraceConfig other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(ProbabilitySampler, other.ProbabilitySampler)) return false; + if (!object.Equals(ConstantSampler, other.ConstantSampler)) return false; + if (!object.Equals(RateLimitingSampler, other.RateLimitingSampler)) return false; + if (MaxNumberOfAttributes != other.MaxNumberOfAttributes) return false; + if (MaxNumberOfAnnotations != other.MaxNumberOfAnnotations) return false; + if (MaxNumberOfMessageEvents != other.MaxNumberOfMessageEvents) return false; + if (MaxNumberOfLinks != other.MaxNumberOfLinks) return false; + if (SamplerCase != other.SamplerCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (samplerCase_ == SamplerOneofCase.ProbabilitySampler) hash ^= ProbabilitySampler.GetHashCode(); + if (samplerCase_ == SamplerOneofCase.ConstantSampler) hash ^= ConstantSampler.GetHashCode(); + if (samplerCase_ == SamplerOneofCase.RateLimitingSampler) hash ^= RateLimitingSampler.GetHashCode(); + if (MaxNumberOfAttributes != 0L) hash ^= MaxNumberOfAttributes.GetHashCode(); + if (MaxNumberOfAnnotations != 0L) hash ^= MaxNumberOfAnnotations.GetHashCode(); + if (MaxNumberOfMessageEvents != 0L) hash ^= MaxNumberOfMessageEvents.GetHashCode(); + if (MaxNumberOfLinks != 0L) hash ^= MaxNumberOfLinks.GetHashCode(); + hash ^= (int) samplerCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (samplerCase_ == SamplerOneofCase.ProbabilitySampler) { + output.WriteRawTag(10); + output.WriteMessage(ProbabilitySampler); + } + if (samplerCase_ == SamplerOneofCase.ConstantSampler) { + output.WriteRawTag(18); + output.WriteMessage(ConstantSampler); + } + if (samplerCase_ == SamplerOneofCase.RateLimitingSampler) { + output.WriteRawTag(26); + output.WriteMessage(RateLimitingSampler); + } + if (MaxNumberOfAttributes != 0L) { + output.WriteRawTag(32); + output.WriteInt64(MaxNumberOfAttributes); + } + if (MaxNumberOfAnnotations != 0L) { + output.WriteRawTag(40); + output.WriteInt64(MaxNumberOfAnnotations); + } + if (MaxNumberOfMessageEvents != 0L) { + output.WriteRawTag(48); + output.WriteInt64(MaxNumberOfMessageEvents); + } + if (MaxNumberOfLinks != 0L) { + output.WriteRawTag(56); + output.WriteInt64(MaxNumberOfLinks); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (samplerCase_ == SamplerOneofCase.ProbabilitySampler) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ProbabilitySampler); + } + if (samplerCase_ == SamplerOneofCase.ConstantSampler) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ConstantSampler); + } + if (samplerCase_ == SamplerOneofCase.RateLimitingSampler) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(RateLimitingSampler); + } + if (MaxNumberOfAttributes != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(MaxNumberOfAttributes); + } + if (MaxNumberOfAnnotations != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(MaxNumberOfAnnotations); + } + if (MaxNumberOfMessageEvents != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(MaxNumberOfMessageEvents); + } + if (MaxNumberOfLinks != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(MaxNumberOfLinks); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(TraceConfig other) { + if (other == null) { + return; + } + if (other.MaxNumberOfAttributes != 0L) { + MaxNumberOfAttributes = other.MaxNumberOfAttributes; + } + if (other.MaxNumberOfAnnotations != 0L) { + MaxNumberOfAnnotations = other.MaxNumberOfAnnotations; + } + if (other.MaxNumberOfMessageEvents != 0L) { + MaxNumberOfMessageEvents = other.MaxNumberOfMessageEvents; + } + if (other.MaxNumberOfLinks != 0L) { + MaxNumberOfLinks = other.MaxNumberOfLinks; + } + switch (other.SamplerCase) { + case SamplerOneofCase.ProbabilitySampler: + if (ProbabilitySampler == null) { + ProbabilitySampler = new global::Opencensus.Proto.Trace.V1.ProbabilitySampler(); + } + ProbabilitySampler.MergeFrom(other.ProbabilitySampler); + break; + case SamplerOneofCase.ConstantSampler: + if (ConstantSampler == null) { + ConstantSampler = new global::Opencensus.Proto.Trace.V1.ConstantSampler(); + } + ConstantSampler.MergeFrom(other.ConstantSampler); + break; + case SamplerOneofCase.RateLimitingSampler: + if (RateLimitingSampler == null) { + RateLimitingSampler = new global::Opencensus.Proto.Trace.V1.RateLimitingSampler(); + } + RateLimitingSampler.MergeFrom(other.RateLimitingSampler); + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + global::Opencensus.Proto.Trace.V1.ProbabilitySampler subBuilder = new global::Opencensus.Proto.Trace.V1.ProbabilitySampler(); + if (samplerCase_ == SamplerOneofCase.ProbabilitySampler) { + subBuilder.MergeFrom(ProbabilitySampler); + } + input.ReadMessage(subBuilder); + ProbabilitySampler = subBuilder; + break; + } + case 18: { + global::Opencensus.Proto.Trace.V1.ConstantSampler subBuilder = new global::Opencensus.Proto.Trace.V1.ConstantSampler(); + if (samplerCase_ == SamplerOneofCase.ConstantSampler) { + subBuilder.MergeFrom(ConstantSampler); + } + input.ReadMessage(subBuilder); + ConstantSampler = subBuilder; + break; + } + case 26: { + global::Opencensus.Proto.Trace.V1.RateLimitingSampler subBuilder = new global::Opencensus.Proto.Trace.V1.RateLimitingSampler(); + if (samplerCase_ == SamplerOneofCase.RateLimitingSampler) { + subBuilder.MergeFrom(RateLimitingSampler); + } + input.ReadMessage(subBuilder); + RateLimitingSampler = subBuilder; + break; + } + case 32: { + MaxNumberOfAttributes = input.ReadInt64(); + break; + } + case 40: { + MaxNumberOfAnnotations = input.ReadInt64(); + break; + } + case 48: { + MaxNumberOfMessageEvents = input.ReadInt64(); + break; + } + case 56: { + MaxNumberOfLinks = input.ReadInt64(); + break; + } + } + } + } + + } + + /// + /// Sampler that tries to uniformly sample traces with a given probability. + /// The probability of sampling a trace is equal to that of the specified probability. + /// + public sealed partial class ProbabilitySampler : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ProbabilitySampler()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceConfigReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ProbabilitySampler() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ProbabilitySampler(ProbabilitySampler other) : this() { + samplingProbability_ = other.samplingProbability_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ProbabilitySampler Clone() { + return new ProbabilitySampler(this); + } + + /// Field number for the "samplingProbability" field. + public const int SamplingProbabilityFieldNumber = 1; + private double samplingProbability_; + /// + /// The desired probability of sampling. Must be within [0.0, 1.0]. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public double SamplingProbability { + get { return samplingProbability_; } + set { + samplingProbability_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ProbabilitySampler); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ProbabilitySampler other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(SamplingProbability, other.SamplingProbability)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (SamplingProbability != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(SamplingProbability); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (SamplingProbability != 0D) { + output.WriteRawTag(9); + output.WriteDouble(SamplingProbability); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (SamplingProbability != 0D) { + size += 1 + 8; + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ProbabilitySampler other) { + if (other == null) { + return; + } + if (other.SamplingProbability != 0D) { + SamplingProbability = other.SamplingProbability; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 9: { + SamplingProbability = input.ReadDouble(); + break; + } + } + } + } + + } + + /// + /// Sampler that makes a constant decision (either always "yes" or always "no") + /// on span sampling. + /// + public sealed partial class ConstantSampler : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ConstantSampler()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceConfigReflection.Descriptor.MessageTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ConstantSampler() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ConstantSampler(ConstantSampler other) : this() { + decision_ = other.decision_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ConstantSampler Clone() { + return new ConstantSampler(this); + } + + /// Field number for the "decision" field. + public const int DecisionFieldNumber = 1; + private bool decision_; + /// + /// Whether spans should be always sampled, or never sampled. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Decision { + get { return decision_; } + set { + decision_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ConstantSampler); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ConstantSampler other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Decision != other.Decision) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Decision != false) hash ^= Decision.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Decision != false) { + output.WriteRawTag(8); + output.WriteBool(Decision); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Decision != false) { + size += 1 + 1; + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ConstantSampler other) { + if (other == null) { + return; + } + if (other.Decision != false) { + Decision = other.Decision; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Decision = input.ReadBool(); + break; + } + } + } + } + + } + + /// + /// Sampler that tries to sample with a rate per time window. + /// + public sealed partial class RateLimitingSampler : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RateLimitingSampler()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Trace.V1.TraceConfigReflection.Descriptor.MessageTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RateLimitingSampler() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RateLimitingSampler(RateLimitingSampler other) : this() { + qps_ = other.qps_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RateLimitingSampler Clone() { + return new RateLimitingSampler(this); + } + + /// Field number for the "qps" field. + public const int QpsFieldNumber = 1; + private long qps_; + /// + /// Rate per second. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long Qps { + get { return qps_; } + set { + qps_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as RateLimitingSampler); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(RateLimitingSampler other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Qps != other.Qps) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Qps != 0L) hash ^= Qps.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Qps != 0L) { + output.WriteRawTag(8); + output.WriteInt64(Qps); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Qps != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(Qps); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(RateLimitingSampler other) { + if (other == null) { + return; + } + if (other.Qps != 0L) { + Qps = other.Qps; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Qps = input.ReadInt64(); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceService.g.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceService.g.cs new file mode 100644 index 000000000..bd1c1edad --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceService.g.cs @@ -0,0 +1,728 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: agent/trace/v1/trace_service.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Opencensus.Proto.Agent.Trace.V1 { + + /// Holder for reflection information generated from agent/trace/v1/trace_service.proto + public static partial class TraceServiceReflection { + + #region Descriptor + /// File descriptor for agent/trace/v1/trace_service.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static TraceServiceReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CiJhZ2VudC90cmFjZS92MS90cmFjZV9zZXJ2aWNlLnByb3RvEh9vcGVuY2Vu", + "c3VzLnByb3RvLmFnZW50LnRyYWNlLnYxGi1vcGVuY2Vuc3VzL3Byb3RvL2Fn", + "ZW50L2NvbW1vbi92MS9jb21tb24ucHJvdG8aK29wZW5jZW5zdXMvcHJvdG8v", + "cmVzb3VyY2UvdjEvcmVzb3VyY2UucHJvdG8aJW9wZW5jZW5zdXMvcHJvdG8v", + "dHJhY2UvdjEvdHJhY2UucHJvdG8aLG9wZW5jZW5zdXMvcHJvdG8vdHJhY2Uv", + "djEvdHJhY2VfY29uZmlnLnByb3RvIoQBChRDdXJyZW50TGlicmFyeUNvbmZp", + "ZxI0CgRub2RlGAEgASgLMiYub3BlbmNlbnN1cy5wcm90by5hZ2VudC5jb21t", + "b24udjEuTm9kZRI2CgZjb25maWcYAiABKAsyJi5vcGVuY2Vuc3VzLnByb3Rv", + "LnRyYWNlLnYxLlRyYWNlQ29uZmlnIoQBChRVcGRhdGVkTGlicmFyeUNvbmZp", + "ZxI0CgRub2RlGAEgASgLMiYub3BlbmNlbnN1cy5wcm90by5hZ2VudC5jb21t", + "b24udjEuTm9kZRI2CgZjb25maWcYAiABKAsyJi5vcGVuY2Vuc3VzLnByb3Rv", + "LnRyYWNlLnYxLlRyYWNlQ29uZmlnIrsBChlFeHBvcnRUcmFjZVNlcnZpY2VS", + "ZXF1ZXN0EjQKBG5vZGUYASABKAsyJi5vcGVuY2Vuc3VzLnByb3RvLmFnZW50", + "LmNvbW1vbi52MS5Ob2RlEi4KBXNwYW5zGAIgAygLMh8ub3BlbmNlbnN1cy5w", + "cm90by50cmFjZS52MS5TcGFuEjgKCHJlc291cmNlGAMgASgLMiYub3BlbmNl", + "bnN1cy5wcm90by5yZXNvdXJjZS52MS5SZXNvdXJjZSIcChpFeHBvcnRUcmFj", + "ZVNlcnZpY2VSZXNwb25zZTKWAgoMVHJhY2VTZXJ2aWNlEnwKBkNvbmZpZxI1", + "Lm9wZW5jZW5zdXMucHJvdG8uYWdlbnQudHJhY2UudjEuQ3VycmVudExpYnJh", + "cnlDb25maWcaNS5vcGVuY2Vuc3VzLnByb3RvLmFnZW50LnRyYWNlLnYxLlVw", + "ZGF0ZWRMaWJyYXJ5Q29uZmlnIgAoATABEocBCgZFeHBvcnQSOi5vcGVuY2Vu", + "c3VzLnByb3RvLmFnZW50LnRyYWNlLnYxLkV4cG9ydFRyYWNlU2VydmljZVJl", + "cXVlc3QaOy5vcGVuY2Vuc3VzLnByb3RvLmFnZW50LnRyYWNlLnYxLkV4cG9y", + "dFRyYWNlU2VydmljZVJlc3BvbnNlIgAoATABQoMBCiJpby5vcGVuY2Vuc3Vz", + "LnByb3RvLmFnZW50LnRyYWNlLnYxQhFUcmFjZVNlcnZpY2VQcm90b1ABWkhn", + "aXRodWIuY29tL2NlbnN1cy1pbnN0cnVtZW50YXRpb24vb3BlbmNlbnN1cy1w", + "cm90by9nZW4tZ28vYWdlbnQvdHJhY2UvdjFiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Opencensus.Proto.Agent.Common.V1.CommonReflection.Descriptor, global::Opencensus.Proto.Resource.V1.ResourceReflection.Descriptor, global::Opencensus.Proto.Trace.V1.TraceReflection.Descriptor, global::Opencensus.Proto.Trace.V1.TraceConfigReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Trace.V1.CurrentLibraryConfig), global::Opencensus.Proto.Agent.Trace.V1.CurrentLibraryConfig.Parser, new[]{ "Node", "Config" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Trace.V1.UpdatedLibraryConfig), global::Opencensus.Proto.Agent.Trace.V1.UpdatedLibraryConfig.Parser, new[]{ "Node", "Config" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Trace.V1.ExportTraceServiceRequest), global::Opencensus.Proto.Agent.Trace.V1.ExportTraceServiceRequest.Parser, new[]{ "Node", "Spans", "Resource" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Opencensus.Proto.Agent.Trace.V1.ExportTraceServiceResponse), global::Opencensus.Proto.Agent.Trace.V1.ExportTraceServiceResponse.Parser, null, null, null, null) + })); + } + #endregion + + } + #region Messages + public sealed partial class CurrentLibraryConfig : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CurrentLibraryConfig()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Trace.V1.TraceServiceReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CurrentLibraryConfig() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CurrentLibraryConfig(CurrentLibraryConfig other) : this() { + node_ = other.node_ != null ? other.node_.Clone() : null; + config_ = other.config_ != null ? other.config_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CurrentLibraryConfig Clone() { + return new CurrentLibraryConfig(this); + } + + /// Field number for the "node" field. + public const int NodeFieldNumber = 1; + private global::Opencensus.Proto.Agent.Common.V1.Node node_; + /// + /// This is required only in the first message on the stream or if the + /// previous sent CurrentLibraryConfig message has a different Node (e.g. + /// when the same RPC is used to configure multiple Applications). + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Agent.Common.V1.Node Node { + get { return node_; } + set { + node_ = value; + } + } + + /// Field number for the "config" field. + public const int ConfigFieldNumber = 2; + private global::Opencensus.Proto.Trace.V1.TraceConfig config_; + /// + /// Current configuration. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TraceConfig Config { + get { return config_; } + set { + config_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as CurrentLibraryConfig); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(CurrentLibraryConfig other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Node, other.Node)) return false; + if (!object.Equals(Config, other.Config)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (node_ != null) hash ^= Node.GetHashCode(); + if (config_ != null) hash ^= Config.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (node_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Node); + } + if (config_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Config); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (node_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Node); + } + if (config_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Config); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(CurrentLibraryConfig other) { + if (other == null) { + return; + } + if (other.node_ != null) { + if (node_ == null) { + node_ = new global::Opencensus.Proto.Agent.Common.V1.Node(); + } + Node.MergeFrom(other.Node); + } + if (other.config_ != null) { + if (config_ == null) { + config_ = new global::Opencensus.Proto.Trace.V1.TraceConfig(); + } + Config.MergeFrom(other.Config); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (node_ == null) { + node_ = new global::Opencensus.Proto.Agent.Common.V1.Node(); + } + input.ReadMessage(node_); + break; + } + case 18: { + if (config_ == null) { + config_ = new global::Opencensus.Proto.Trace.V1.TraceConfig(); + } + input.ReadMessage(config_); + break; + } + } + } + } + + } + + public sealed partial class UpdatedLibraryConfig : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UpdatedLibraryConfig()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Trace.V1.TraceServiceReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UpdatedLibraryConfig() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UpdatedLibraryConfig(UpdatedLibraryConfig other) : this() { + node_ = other.node_ != null ? other.node_.Clone() : null; + config_ = other.config_ != null ? other.config_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public UpdatedLibraryConfig Clone() { + return new UpdatedLibraryConfig(this); + } + + /// Field number for the "node" field. + public const int NodeFieldNumber = 1; + private global::Opencensus.Proto.Agent.Common.V1.Node node_; + /// + /// This field is ignored when the RPC is used to configure only one Application. + /// This is required only in the first message on the stream or if the + /// previous sent UpdatedLibraryConfig message has a different Node. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Agent.Common.V1.Node Node { + get { return node_; } + set { + node_ = value; + } + } + + /// Field number for the "config" field. + public const int ConfigFieldNumber = 2; + private global::Opencensus.Proto.Trace.V1.TraceConfig config_; + /// + /// Requested updated configuration. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Trace.V1.TraceConfig Config { + get { return config_; } + set { + config_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as UpdatedLibraryConfig); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(UpdatedLibraryConfig other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Node, other.Node)) return false; + if (!object.Equals(Config, other.Config)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (node_ != null) hash ^= Node.GetHashCode(); + if (config_ != null) hash ^= Config.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (node_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Node); + } + if (config_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Config); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (node_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Node); + } + if (config_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Config); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(UpdatedLibraryConfig other) { + if (other == null) { + return; + } + if (other.node_ != null) { + if (node_ == null) { + node_ = new global::Opencensus.Proto.Agent.Common.V1.Node(); + } + Node.MergeFrom(other.Node); + } + if (other.config_ != null) { + if (config_ == null) { + config_ = new global::Opencensus.Proto.Trace.V1.TraceConfig(); + } + Config.MergeFrom(other.Config); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (node_ == null) { + node_ = new global::Opencensus.Proto.Agent.Common.V1.Node(); + } + input.ReadMessage(node_); + break; + } + case 18: { + if (config_ == null) { + config_ = new global::Opencensus.Proto.Trace.V1.TraceConfig(); + } + input.ReadMessage(config_); + break; + } + } + } + } + + } + + public sealed partial class ExportTraceServiceRequest : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ExportTraceServiceRequest()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Trace.V1.TraceServiceReflection.Descriptor.MessageTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ExportTraceServiceRequest() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ExportTraceServiceRequest(ExportTraceServiceRequest other) : this() { + node_ = other.node_ != null ? other.node_.Clone() : null; + spans_ = other.spans_.Clone(); + resource_ = other.resource_ != null ? other.resource_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ExportTraceServiceRequest Clone() { + return new ExportTraceServiceRequest(this); + } + + /// Field number for the "node" field. + public const int NodeFieldNumber = 1; + private global::Opencensus.Proto.Agent.Common.V1.Node node_; + /// + /// This is required only in the first message on the stream or if the + /// previous sent ExportTraceServiceRequest message has a different Node (e.g. + /// when the same RPC is used to send Spans from multiple Applications). + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Agent.Common.V1.Node Node { + get { return node_; } + set { + node_ = value; + } + } + + /// Field number for the "spans" field. + public const int SpansFieldNumber = 2; + private static readonly pb::FieldCodec _repeated_spans_codec + = pb::FieldCodec.ForMessage(18, global::Opencensus.Proto.Trace.V1.Span.Parser); + private readonly pbc::RepeatedField spans_ = new pbc::RepeatedField(); + /// + /// A list of Spans that belong to the last received Node. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pbc::RepeatedField Spans { + get { return spans_; } + } + + /// Field number for the "resource" field. + public const int ResourceFieldNumber = 3; + private global::Opencensus.Proto.Resource.V1.Resource resource_; + /// + /// The resource for the spans in this message that do not have an explicit + /// resource set. + /// If unset, the most recently set resource in the RPC stream applies. It is + /// valid to never be set within a stream, e.g. when no resource info is known. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Opencensus.Proto.Resource.V1.Resource Resource { + get { return resource_; } + set { + resource_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ExportTraceServiceRequest); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ExportTraceServiceRequest other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Node, other.Node)) return false; + if(!spans_.Equals(other.spans_)) return false; + if (!object.Equals(Resource, other.Resource)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (node_ != null) hash ^= Node.GetHashCode(); + hash ^= spans_.GetHashCode(); + if (resource_ != null) hash ^= Resource.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (node_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Node); + } + spans_.WriteTo(output, _repeated_spans_codec); + if (resource_ != null) { + output.WriteRawTag(26); + output.WriteMessage(Resource); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (node_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Node); + } + size += spans_.CalculateSize(_repeated_spans_codec); + if (resource_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Resource); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ExportTraceServiceRequest other) { + if (other == null) { + return; + } + if (other.node_ != null) { + if (node_ == null) { + node_ = new global::Opencensus.Proto.Agent.Common.V1.Node(); + } + Node.MergeFrom(other.Node); + } + spans_.Add(other.spans_); + if (other.resource_ != null) { + if (resource_ == null) { + resource_ = new global::Opencensus.Proto.Resource.V1.Resource(); + } + Resource.MergeFrom(other.Resource); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (node_ == null) { + node_ = new global::Opencensus.Proto.Agent.Common.V1.Node(); + } + input.ReadMessage(node_); + break; + } + case 18: { + spans_.AddEntriesFrom(input, _repeated_spans_codec); + break; + } + case 26: { + if (resource_ == null) { + resource_ = new global::Opencensus.Proto.Resource.V1.Resource(); + } + input.ReadMessage(resource_); + break; + } + } + } + } + + } + + public sealed partial class ExportTraceServiceResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ExportTraceServiceResponse()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Opencensus.Proto.Agent.Trace.V1.TraceServiceReflection.Descriptor.MessageTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ExportTraceServiceResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ExportTraceServiceResponse(ExportTraceServiceResponse other) : this() { + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ExportTraceServiceResponse Clone() { + return new ExportTraceServiceResponse(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ExportTraceServiceResponse); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ExportTraceServiceResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ExportTraceServiceResponse other) { + if (other == null) { + return; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceServiceGrpc.cs b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceServiceGrpc.cs new file mode 100644 index 000000000..6174ad39d --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/Implementation/gen/TraceServiceGrpc.cs @@ -0,0 +1,180 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: agent/trace/v1/trace_service.proto +// +// Original file comments: +// Copyright 2018, OpenCensus 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. +// +#pragma warning disable 0414, 1591 +#region Designer generated code + +using grpc = global::Grpc.Core; + +namespace Opencensus.Proto.Agent.Trace.V1 { + /// + /// Service that can be used to push spans and configs between one Application + /// instrumented with OpenCensus and an agent, or between an agent and a + /// central collector or config service (in this case spans and configs are + /// sent/received to/from multiple Applications). + /// + public static partial class TraceService + { + static readonly string __ServiceName = "opencensus.proto.agent.trace.v1.TraceService"; + + static readonly grpc::Marshaller __Marshaller_opencensus_proto_agent_trace_v1_CurrentLibraryConfig = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Opencensus.Proto.Agent.Trace.V1.CurrentLibraryConfig.Parser.ParseFrom); + static readonly grpc::Marshaller __Marshaller_opencensus_proto_agent_trace_v1_UpdatedLibraryConfig = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Opencensus.Proto.Agent.Trace.V1.UpdatedLibraryConfig.Parser.ParseFrom); + static readonly grpc::Marshaller __Marshaller_opencensus_proto_agent_trace_v1_ExportTraceServiceRequest = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Opencensus.Proto.Agent.Trace.V1.ExportTraceServiceRequest.Parser.ParseFrom); + static readonly grpc::Marshaller __Marshaller_opencensus_proto_agent_trace_v1_ExportTraceServiceResponse = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Opencensus.Proto.Agent.Trace.V1.ExportTraceServiceResponse.Parser.ParseFrom); + + static readonly grpc::Method __Method_Config = new grpc::Method( + grpc::MethodType.DuplexStreaming, + __ServiceName, + "Config", + __Marshaller_opencensus_proto_agent_trace_v1_CurrentLibraryConfig, + __Marshaller_opencensus_proto_agent_trace_v1_UpdatedLibraryConfig); + + static readonly grpc::Method __Method_Export = new grpc::Method( + grpc::MethodType.DuplexStreaming, + __ServiceName, + "Export", + __Marshaller_opencensus_proto_agent_trace_v1_ExportTraceServiceRequest, + __Marshaller_opencensus_proto_agent_trace_v1_ExportTraceServiceResponse); + + /// Service descriptor + public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor + { + get { return global::Opencensus.Proto.Agent.Trace.V1.TraceServiceReflection.Descriptor.Services[0]; } + } + + /// Base class for server-side implementations of TraceService + public abstract partial class TraceServiceBase + { + /// + /// After initialization, this RPC must be kept alive for the entire life of + /// the application. The agent pushes configs down to applications via a + /// stream. + /// + /// Used for reading requests from the client. + /// Used for sending responses back to the client. + /// The context of the server-side call handler being invoked. + /// A task indicating completion of the handler. + public virtual global::System.Threading.Tasks.Task Config(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } + + /// + /// For performance reasons, it is recommended to keep this RPC + /// alive for the entire life of the application. + /// + /// Used for reading requests from the client. + /// Used for sending responses back to the client. + /// The context of the server-side call handler being invoked. + /// A task indicating completion of the handler. + public virtual global::System.Threading.Tasks.Task Export(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } + + } + + /// Client for TraceService + public partial class TraceServiceClient : grpc::ClientBase + { + /// Creates a new client for TraceService + /// The channel to use to make remote calls. + public TraceServiceClient(grpc::Channel channel) : base(channel) + { + } + /// Creates a new client for TraceService that uses a custom CallInvoker. + /// The callInvoker to use to make remote calls. + public TraceServiceClient(grpc::CallInvoker callInvoker) : base(callInvoker) + { + } + /// Protected parameterless constructor to allow creation of test doubles. + protected TraceServiceClient() : base() + { + } + /// Protected constructor to allow creation of configured clients. + /// The client configuration. + protected TraceServiceClient(ClientBaseConfiguration configuration) : base(configuration) + { + } + + /// + /// After initialization, this RPC must be kept alive for the entire life of + /// the application. The agent pushes configs down to applications via a + /// stream. + /// + /// The initial metadata to send with the call. This parameter is optional. + /// An optional deadline for the call. The call will be cancelled if deadline is hit. + /// An optional token for canceling the call. + /// The call object. + public virtual grpc::AsyncDuplexStreamingCall Config(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return Config(new grpc::CallOptions(headers, deadline, cancellationToken)); + } + /// + /// After initialization, this RPC must be kept alive for the entire life of + /// the application. The agent pushes configs down to applications via a + /// stream. + /// + /// The options for the call. + /// The call object. + public virtual grpc::AsyncDuplexStreamingCall Config(grpc::CallOptions options) + { + return CallInvoker.AsyncDuplexStreamingCall(__Method_Config, null, options); + } + /// + /// For performance reasons, it is recommended to keep this RPC + /// alive for the entire life of the application. + /// + /// The initial metadata to send with the call. This parameter is optional. + /// An optional deadline for the call. The call will be cancelled if deadline is hit. + /// An optional token for canceling the call. + /// The call object. + public virtual grpc::AsyncDuplexStreamingCall Export(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return Export(new grpc::CallOptions(headers, deadline, cancellationToken)); + } + /// + /// For performance reasons, it is recommended to keep this RPC + /// alive for the entire life of the application. + /// + /// The options for the call. + /// The call object. + public virtual grpc::AsyncDuplexStreamingCall Export(grpc::CallOptions options) + { + return CallInvoker.AsyncDuplexStreamingCall(__Method_Export, null, options); + } + /// Creates a new instance of client from given ClientBaseConfiguration. + protected override TraceServiceClient NewInstance(ClientBaseConfiguration configuration) + { + return new TraceServiceClient(configuration); + } + } + + /// Creates service definition that can be registered with a server + /// An object implementing the server-side handling logic. + public static grpc::ServerServiceDefinition BindService(TraceServiceBase serviceImpl) + { + return grpc::ServerServiceDefinition.CreateBuilder() + .AddMethod(__Method_Config, serviceImpl.Config) + .AddMethod(__Method_Export, serviceImpl.Export).Build(); + } + + } +} +#endregion diff --git a/src/OpenCensus.Exporter.Ocagent/OcagentExporter.cs b/src/OpenCensus.Exporter.Ocagent/OcagentExporter.cs new file mode 100644 index 000000000..9244eac01 --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/OcagentExporter.cs @@ -0,0 +1,100 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Ocagent +{ + using Grpc.Core; + + using OpenCensus.Exporter.Ocagent.Implementation; + using OpenCensus.Trace.Export; + + /// + /// Exporter of Open Census traces to the Ocagent or LocalForwarder. + /// + public class OcagentExporter + { + private const string TraceExporterName = "OcagentTraceExporter"; + + private readonly IExportComponent exportComponent; + + private readonly object lck = new object(); + + private readonly string agentEndpoint; + private readonly string hostName; + private readonly string serviceName; + private TraceExporterHandler handler; + + /// + /// Initializes a new instance of the class. + /// This exporter allows to send Open Census data to OpenCensus service or LocalForwarder. + /// + /// Exporter to get traces from. + /// Agent endpoint in the host:port format. + /// Name of the host. + /// Name of the application. + public OcagentExporter( + IExportComponent exportComponent, + string agentEndpoint, + string hostName, + string serviceName) + { + this.exportComponent = exportComponent; + this.agentEndpoint = agentEndpoint; + this.hostName = hostName; + this.serviceName = serviceName; + } + + /// + /// Start exporter. + /// + public void Start() + { + lock (this.lck) + { + if (this.handler != null) + { + return; + } + + this.handler = new TraceExporterHandler( + this.agentEndpoint, + this.hostName, + this.serviceName, + ChannelCredentials.Insecure); + + this.exportComponent.SpanExporter.RegisterHandler(TraceExporterName, this.handler); + } + } + + /// + /// Stop exporter. + /// + public void Stop() + { + lock (this.lck) + { + if (this.handler == null) + { + return; + } + + this.exportComponent.SpanExporter.UnregisterHandler(TraceExporterName); + this.handler.Dispose(); + this.handler = null; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Ocagent/OpenCensus.Exporter.Ocagent.csproj b/src/OpenCensus.Exporter.Ocagent/OpenCensus.Exporter.Ocagent.csproj new file mode 100644 index 000000000..8215f9450 --- /dev/null +++ b/src/OpenCensus.Exporter.Ocagent/OpenCensus.Exporter.Ocagent.csproj @@ -0,0 +1,28 @@ + + + + + net46;netstandard2.0 + netstandard2.0 + True + + + + OcAgent exporter for OpenCensus + Tracing;OpenCensus;Management;Monitoring;ocagent;distributed-tracing + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + + + + + All + + + diff --git a/src/OpenCensus.Exporter.Prometheus/AssemblyInfo.cs b/src/OpenCensus.Exporter.Prometheus/AssemblyInfo.cs new file mode 100644 index 000000000..6a81089a0 --- /dev/null +++ b/src/OpenCensus.Exporter.Prometheus/AssemblyInfo.cs @@ -0,0 +1,17 @@ +// +// Copyright 2018, OpenCensus 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. +// + +[assembly: System.CLSCompliant(true)] diff --git a/src/OpenCensus.Exporter.Prometheus/Implementation/MetricsHttpServer.cs b/src/OpenCensus.Exporter.Prometheus/Implementation/MetricsHttpServer.cs new file mode 100644 index 000000000..4734c48fa --- /dev/null +++ b/src/OpenCensus.Exporter.Prometheus/Implementation/MetricsHttpServer.cs @@ -0,0 +1,118 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Prometheus.Implementation +{ + using System; + using System.IO; + using System.Net; + using System.Threading; + using OpenCensus.Stats; + + internal class MetricsHttpServer + { + private readonly IViewManager viewManager; + + private readonly CancellationToken token; + + private readonly HttpListener httpListener = new HttpListener(); + + public MetricsHttpServer(IViewManager viewManager, PrometheusExporterOptions options, CancellationToken token) + { + this.viewManager = viewManager; + this.token = token; + this.httpListener.Prefixes.Add(options.Url.ToString()); + } + + public void WorkerThread() + { + this.httpListener.Start(); + + try + { + while (!this.token.IsCancellationRequested) + { + var ctxTask = this.httpListener.GetContextAsync(); + ctxTask.Wait(this.token); + + var ctx = ctxTask.Result; + + ctx.Response.StatusCode = 200; + ctx.Response.ContentType = PrometheusMetricBuilder.ContentType; + + using (var output = ctx.Response.OutputStream) + { + using (var writer = new StreamWriter(output)) + { + foreach (var view in this.viewManager.AllExportedViews) + { + var data = this.viewManager.GetView(view.Name); + + var builder = new PrometheusMetricBuilder() + .WithName(data.View.Name.AsString) + .WithDescription(data.View.Description); + + builder = data.View.Aggregation.Match( + (agg) => { return builder.WithType("gauge"); }, // Func p0 + (agg) => { return builder.WithType("counter"); }, // Func< ICount, M > p1, + (agg) => { return builder.WithType("histogram"); }, // Func p2, + (agg) => { return builder.WithType("histogram"); }, // Func< IDistribution, M > p3, + (agg) => { return builder.WithType("gauge"); }, // Func p4, + (agg) => { return builder.WithType("gauge"); }); // Func< IAggregation, M > p6); + + foreach (var value in data.AggregationMap) + { + var metricValueBuilder = builder.AddValue(); + + // TODO: This is not optimal. Need to refactor to split builder into separate functions + metricValueBuilder = value.Value.Match( + metricValueBuilder.WithValue, + metricValueBuilder.WithValue, + metricValueBuilder.WithValue, + metricValueBuilder.WithValue, + metricValueBuilder.WithValue, + metricValueBuilder.WithValue, + metricValueBuilder.WithValue, + metricValueBuilder.WithValue); + + for (int i = 0; i < value.Key.Values.Count; i++) + { + metricValueBuilder.WithLabel(data.View.Columns[i].Name, value.Key.Values[i].AsString); + } + } + + builder.Write(writer); + } + } + } + } + } + catch (OperationCanceledException) + { + // this will happen when cancellation will be requested + } + catch (Exception) + { + // TODO: report error + } + finally + { + this.httpListener.Stop(); + this.httpListener.Close(); + } + } + } +} diff --git a/src/OpenCensus.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs b/src/OpenCensus.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs new file mode 100644 index 000000000..53e71013d --- /dev/null +++ b/src/OpenCensus.Exporter.Prometheus/Implementation/PrometheusMetricBuilder.cs @@ -0,0 +1,385 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Prometheus.Implementation +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using OpenCensus.Stats; + using OpenCensus.Stats.Aggregations; + + internal class PrometheusMetricBuilder + { + public const string ContentType = "text/plain; version = 0.0.4"; + + private static char[] firstCharacterNameCharset = new char[] + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '_', ':', + }; + + private static char[] nameCharset = new char[] + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '_', ':', + }; + + private static char[] firstCharacterLabelCharset = new char[] + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '_', + }; + + private static char[] labelCharset = new char[] + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '_', + }; + + private readonly ICollection values = new List(); + + private string name; + private string description; + private string type; + + public PrometheusMetricBuilder WithName(string name) + { + this.name = name; + return this; + } + + public PrometheusMetricBuilder WithDescription(string description) + { + this.description = description; + return this; + } + + public PrometheusMetricBuilder WithType(string type) + { + this.type = type; + return this; + } + + public PrometheusMetricValueBuilder AddValue() + { + var val = new PrometheusMetricValueBuilder(); + + this.values.Add(val); + + return val; + } + + public void Write(StreamWriter writer) + { + // https://prometheus.io/docs/instrumenting/exposition_formats/ + + if (string.IsNullOrEmpty(this.name)) + { + throw new InvalidOperationException("Metric name should not be empty"); + } + + this.name = GetSafeMetricName(this.name); + + if (!string.IsNullOrEmpty(this.description)) + { + // Lines with a # as the first non-whitespace character are comments. + // They are ignored unless the first token after # is either HELP or TYPE. + // Those lines are treated as follows: If the token is HELP, at least one + // more token is expected, which is the metric name. All remaining tokens + // are considered the docstring for that metric name. HELP lines may contain + // any sequence of UTF-8 characters (after the metric name), but the backslash + // and the line feed characters have to be escaped as \\ and \n, respectively. + // Only one HELP line may exist for any given metric name. + + writer.Write("# HELP "); + writer.Write(this.name); + writer.Write(GetSafeMetricDescription(this.description)); + writer.Write("\n"); + } + + if (string.IsNullOrEmpty(this.type)) + { + // If the token is TYPE, exactly two more tokens are expected. The first is the + // metric name, and the second is either counter, gauge, histogram, summary, or + // untyped, defining the type for the metric of that name. Only one TYPE line + // may exist for a given metric name. The TYPE line for a metric name must appear + // before the first sample is reported for that metric name. If there is no TYPE + // line for a metric name, the type is set to untyped. + + writer.Write("# HELP "); + writer.Write(this.name); + writer.Write(this.type); + writer.Write("\n"); + } + + // The remaining lines describe samples (one per line) using the following syntax (EBNF): + // metric_name [ + // "{" label_name "=" `"` label_value `"` { "," label_name "=" `"` label_value `"` } [ "," ] "}" + // ] value [ timestamp ] + // In the sample syntax: + + foreach (var m in this.values) + { + // metric_name and label_name carry the usual Prometheus expression language restrictions. + writer.Write(this.name); + + // label_value can be any sequence of UTF-8 characters, but the backslash + // (\, double-quote ("}, and line feed (\n) characters have to be escaped + // as \\, \", and \n, respectively. + + if (m.Labels.Count > 0) + { + writer.Write(@"{"); + var isFirst = true; + + foreach (var l in m.Labels) + { + if (isFirst) + { + isFirst = false; + } + else + { + writer.Write(","); + } + + var safeKey = GetSafeLabelName(l.Item1); + var safeValue = GetSafeLabelValue(l.Item2); + writer.Write(safeKey); + writer.Write("=\""); + writer.Write(safeValue); + writer.Write("\""); + } + + writer.Write(@"}"); + } + + // value is a float represented as required by Go's ParseFloat() function. In addition to + // standard numerical values, Nan, +Inf, and -Inf are valid values representing not a number, + // positive infinity, and negative infinity, respectively. + writer.Write(" "); + writer.Write(m.Value); + writer.Write(" "); + + // The timestamp is an int64 (milliseconds since epoch, i.e. 1970-01-01 00:00:00 UTC, excluding + // leap seconds), represented as required by Go's ParseInt() function. + writer.Write(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString()); + + // Prometheus' text-based format is line oriented. Lines are separated + // by a line feed character (\n). The last line must end with a line + // feed character. Empty lines are ignored. + writer.Write("\n"); + } + } + + private static string GetSafeMetricName(string name) + { + // https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels + // + // Metric names and labels + // Every time series is uniquely identified by its metric name and a set of key-value pairs, also known as labels. + // The metric name specifies the general feature of a system that is measured (e.g. http_requests_total - the total number of HTTP requests received). It may contain ASCII letters and digits, as well as underscores and colons. It must match the regex [a-zA-Z_:][a-zA-Z0-9_:]*. + // Note: The colons are reserved for user defined recording rules. They should not be used by exporters or direct instrumentation. + // Labels enable Prometheus's dimensional data model: any given combination of labels for the same metric name identifies a particular dimensional instantiation of that metric (for example: all HTTP requests that used the method POST to the /api/tracks handler). The query language allows filtering and aggregation based on these dimensions. Changing any label value, including adding or removing a label, will create a new time series. + // Label names may contain ASCII letters, numbers, as well as underscores. They must match the regex [a-zA-Z_][a-zA-Z0-9_]*. Label names beginning with __ are reserved for internal use. + // Label values may contain any Unicode characters. + + StringBuilder sb = new StringBuilder(); + + if (!string.IsNullOrEmpty(name)) + { + var firstChar = name[0]; + if (firstCharacterNameCharset.Contains(firstChar)) + { + sb.Append(firstChar); + } + else + { + firstChar = firstChar.ToString().ToLowerInvariant()[0]; + + if (firstCharacterNameCharset.Contains(firstChar)) + { + sb.Append(firstChar); + } + else + { + // fallback character + sb.Append('_'); + } + } + } + + for (int i = 1; i < name.Length; ++i) + { + char c = name[i]; + + if (nameCharset.Contains(c)) + { + sb.Append(c); + } + else + { + // fallback character + sb.Append('_'); + } + } + + return sb.ToString(); + } + + private static string GetSafeLabelName(string name) + { + // https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels + // + // Metric names and labels + // Every time series is uniquely identified by its metric name and a set of key-value pairs, also known as labels. + // The metric name specifies the general feature of a system that is measured (e.g. http_requests_total - the total number of HTTP requests received). It may contain ASCII letters and digits, as well as underscores and colons. It must match the regex [a-zA-Z_:][a-zA-Z0-9_:]*. + // Note: The colons are reserved for user defined recording rules. They should not be used by exporters or direct instrumentation. + // Labels enable Prometheus's dimensional data model: any given combination of labels for the same metric name identifies a particular dimensional instantiation of that metric (for example: all HTTP requests that used the method POST to the /api/tracks handler). The query language allows filtering and aggregation based on these dimensions. Changing any label value, including adding or removing a label, will create a new time series. + // Label names may contain ASCII letters, numbers, as well as underscores. They must match the regex [a-zA-Z_][a-zA-Z0-9_]*. Label names beginning with __ are reserved for internal use. + // Label values may contain any Unicode characters. + + StringBuilder sb = new StringBuilder(); + + if (!string.IsNullOrEmpty(name)) + { + var firstChar = name[0]; + if (firstCharacterLabelCharset.Contains(firstChar)) + { + sb.Append(firstChar); + } + else + { + firstChar = firstChar.ToString().ToLowerInvariant()[0]; + + if (firstCharacterLabelCharset.Contains(firstChar)) + { + sb.Append(firstChar); + } + else + { + // fallback character + sb.Append('_'); + } + } + } + + for (int i = 1; i < name.Length; ++i) + { + char c = name[i]; + + if (labelCharset.Contains(c)) + { + sb.Append(c); + } + else + { + // fallback character + sb.Append('_'); + } + } + + return sb.ToString(); + } + + private static string GetSafeLabelValue(string value) + { + // label_value can be any sequence of UTF-8 characters, but the backslash + // (\), double-quote ("), and line feed (\n) characters have to be escaped + // as \\, \", and \n, respectively. + + var result = value.Replace("\\", "\\\\"); + result = result.Replace("\n", "\\n"); + result = result.Replace("\"", "\\\""); + + return result; + } + + private static string GetSafeMetricDescription(string description) + { + // HELP lines may contain any sequence of UTF-8 characters(after the metric name), but the backslash + // and the line feed characters have to be escaped as \\ and \n, respectively.Only one HELP line may + // exist for any given metric name. + var result = description.Replace(@"\", @"\\"); + result = result.Replace("\n", @"\n"); + + return result; + } + + internal class PrometheusMetricValueBuilder + { + public readonly ICollection> Labels = new List>(); + public double Value; + + public PrometheusMetricValueBuilder WithLabel(string name, string value) + { + this.Labels.Add(new Tuple(name, value)); + return this; + } + + public PrometheusMetricValueBuilder WithValue(IAggregationData metric) + { + // TODO: review conversions + // counter, gauge, histogram, summary, or untyped + if (metric is ISumDataDouble doubleSum) + { + this.Value = doubleSum.Sum; + } + else if (metric is ISumDataLong longSum) + { + this.Value = longSum.Sum; + } + else if (metric is ICountData count) + { + this.Value = count.Count; + } + else if (metric is IMeanData mean) + { + // TODO: do more with this + this.Value = mean.Mean; + } + else if (metric is IDistributionData dist) + { + // TODO: do more with this + this.Value = dist.Mean; + } + else if (metric is ILastValueDataDouble lastDoubleValue) + { + this.Value = lastDoubleValue.LastValue; + } + else if (metric is ILastValueDataLong lastLongValue) + { + this.Value = lastLongValue.LastValue; + } + else if (metric is IAggregationData aggregationData) + { + // TODO: report an error + } + + return this; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Prometheus/OpenCensus.Exporter.Prometheus.csproj b/src/OpenCensus.Exporter.Prometheus/OpenCensus.Exporter.Prometheus.csproj new file mode 100644 index 000000000..7015d5374 --- /dev/null +++ b/src/OpenCensus.Exporter.Prometheus/OpenCensus.Exporter.Prometheus.csproj @@ -0,0 +1,28 @@ + + + + + net46;netstandard2.0 + netstandard2.0 + + + + OpenCensus to Prometheus exporter. + True + OpenCensus;Management;Monitoring;Prometheus + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + + + + All + + + + diff --git a/src/OpenCensus.Exporter.Prometheus/PrometheusExporter.cs b/src/OpenCensus.Exporter.Prometheus/PrometheusExporter.cs new file mode 100644 index 000000000..1840db7a5 --- /dev/null +++ b/src/OpenCensus.Exporter.Prometheus/PrometheusExporter.cs @@ -0,0 +1,90 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Prometheus +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using OpenCensus.Exporter.Prometheus.Implementation; + using OpenCensus.Stats; + + /// + /// Exporter of Open Census traces and metrics to Azure Application Insights. + /// + public class PrometheusExporter + { + private readonly IViewManager viewManager; + + private readonly PrometheusExporterOptions options; + + private readonly object lck = new object(); + + private CancellationTokenSource tokenSource; + + private Task workerThread; + + /// + /// Initializes a new instance of the class. + /// + /// Options for the exporter. + /// View manager to get stats from. + public PrometheusExporter(PrometheusExporterOptions options, IViewManager viewManager) + { + this.options = options; + this.viewManager = viewManager; + } + + /// + /// Start exporter. + /// + public void Start() + { + lock (this.lck) + { + if (this.tokenSource != null) + { + return; + } + + this.tokenSource = new CancellationTokenSource(); + + CancellationToken token = this.tokenSource.Token; + + var metricsServer = new MetricsHttpServer(this.viewManager, this.options, token); + this.workerThread = Task.Factory.StartNew((Action)metricsServer.WorkerThread, TaskCreationOptions.LongRunning); + } + } + + /// + /// Stop exporter. + /// + public void Stop() + { + lock (this.lck) + { + if (this.tokenSource == null) + { + return; + } + + this.tokenSource.Cancel(); + this.workerThread.Wait(); + this.tokenSource = null; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Prometheus/PrometheusExporterOptions.cs b/src/OpenCensus.Exporter.Prometheus/PrometheusExporterOptions.cs new file mode 100644 index 000000000..35226f46e --- /dev/null +++ b/src/OpenCensus.Exporter.Prometheus/PrometheusExporterOptions.cs @@ -0,0 +1,31 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Prometheus +{ + using System; + + /// + /// Options to run prometheus exporter. + /// + public class PrometheusExporterOptions + { + /// + /// Gets or sets the port to listen to. Typically it ends with /metrics like http://localhost:9184/metrics/. + /// + public Uri Url { get; set; } + } +} diff --git a/src/OpenCensus.Exporter.Stackdriver/AssemblyInfo.cs b/src/OpenCensus.Exporter.Stackdriver/AssemblyInfo.cs new file mode 100644 index 000000000..a6f90b3e6 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/AssemblyInfo.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("OpenCensus.Exporter.Stackdriver.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif diff --git a/src/OpenCensus.Exporter.Stackdriver/Implementation/Constants.cs b/src/OpenCensus.Exporter.Stackdriver/Implementation/Constants.cs new file mode 100644 index 000000000..face2650b --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Implementation/Constants.cs @@ -0,0 +1,54 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Implementation +{ + using System; + + internal class Constants + { + public const string PACKAGE_VERSION_UNDEFINED = "undefined"; + + public const string LABEL_DESCRIPTION = "OpenCensus TagKey"; + public const string OPENCENSUS_TASK = "opencensus_task"; + public const string OPENCENSUS_TASK_DESCRIPTION = "Opencensus task identifier"; + + public const string GCP_GKE_CONTAINER = "k8s_container"; + public const string GCP_GCE_INSTANCE = "gce_instance"; + public const string AWS_EC2_INSTANCE = "aws_ec2_instance"; + public const string GLOBAL = "global"; + + public const string PROJECT_ID_LABEL_KEY = "project_id"; + public static readonly string OPENCENSUS_TASK_VALUE_DEFAULT = GenerateDefaultTaskValue(); + + public const string GCP_GCE_INSTANCE_TYPE = "cloud.google.com/gce/instance"; + public const string GCP_INSTANCE_ID_KEY = "cloud.google.com/gce/instance_id"; + public const string GCP_ACCOUNT_ID_KEY = "cloud.google.com/gce/project_id"; + public const string GCP_ZONE_KEY = "cloud.google.com/gce/zone"; + + public const string K8S_CONTAINER_TYPE = "k8s.io/container"; + public const string K8S_CLUSTER_NAME_KEY = "k8s.io/cluster/name"; + public const string K8S_CONTAINER_NAME_KEY = "k8s.io/container/name"; + public const string K8S_NAMESPACE_NAME_KEY = "k8s.io/namespace/name"; + public const string K8S_POD_NAME_KEY = "k8s.io/pod/name"; + + private static string GenerateDefaultTaskValue() + { + // Something like '@' + return $"dotnet-{System.Diagnostics.Process.GetCurrentProcess().Id}@{Environment.MachineName}"; + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Exporter.Stackdriver/Implementation/ExporterStackdriverEventSource.cs b/src/OpenCensus.Exporter.Stackdriver/Implementation/ExporterStackdriverEventSource.cs new file mode 100644 index 000000000..3b1a9912f --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Implementation/ExporterStackdriverEventSource.cs @@ -0,0 +1,78 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Implementation +{ + using System; + using System.Diagnostics.Tracing; + using System.Globalization; + using System.Threading; + + [EventSource(Name = "OpenCensus-Exporter-Stackdriver")] + internal class ExporterStackdriverEventSource : EventSource + { + public static readonly ExporterStackdriverEventSource Log = new ExporterStackdriverEventSource(); + + [NonEvent] + public void UnknownProblemInWorkerThreadError(Exception ex) + { + if (Log.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.UnknownProblemInWorkerThreadError(ToInvariantString(ex)); + } + } + + [Event(1, Message = "Stackdriver exporter encountered an unknown error and will shut down. Exception: {0}", Level = EventLevel.Error)] + public void UnknownProblemInWorkerThreadError(string ex) + { + this.WriteEvent(1, ex); + } + + [NonEvent] + public void UnknownProblemWhileCreatingStackdriverTimeSeriesError(Exception ex) + { + if (Log.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.UnknownProblemWhileCreatingStackdriverTimeSeriesError(ToInvariantString(ex)); + } + } + + [Event(2, Message = "Stackdriver exporter failed to create time series. Time series will be lost. Exception: {0}", Level = EventLevel.Error)] + public void UnknownProblemWhileCreatingStackdriverTimeSeriesError(string ex) + { + this.WriteEvent(2, ex); + } + + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + private static string ToInvariantString(Exception exception) + { + CultureInfo originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Stackdriver/Implementation/GoogleCloudResourceUtils.cs b/src/OpenCensus.Exporter.Stackdriver/Implementation/GoogleCloudResourceUtils.cs new file mode 100644 index 000000000..883cac637 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Implementation/GoogleCloudResourceUtils.cs @@ -0,0 +1,77 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Implementation +{ + using Google.Api; + using System; + using System.Collections.Generic; + using System.IO; + + /// + /// Utility methods for working with Google Cloud Resources + /// + public static class GoogleCloudResourceUtils + { + /// + /// Detects Google Cloud ProjectId based on the environment on which the code runs. + /// Supports GCE/GKE/GAE and projectId tied to service account + /// In case the code runs in a different environment, + /// the method returns null + /// + /// Google Cloud Project ID + public static string GetProjectId() + { + // Try to detect projectId from the environment where the code is running + var instance = Google.Api.Gax.Platform.Instance(); + var projectId = instance?.ProjectId; + if (!string.IsNullOrEmpty(projectId)) + { + return projectId; + } + + // Try to detect projectId from service account credential if it exists + string serviceAccountFilePath = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS"); + if (!string.IsNullOrEmpty(serviceAccountFilePath) && File.Exists(serviceAccountFilePath)) + { + using (var stream = new FileStream(serviceAccountFilePath, FileMode.Open, FileAccess.Read)) + { + var credential = Google.Apis.Auth.OAuth2.ServiceAccountCredential.FromServiceAccountData(stream); + return credential.ProjectId; + } + } + + projectId = Environment.GetEnvironmentVariable("GOOGLE_PROJECT_ID"); + return projectId; + } + + /// + /// Determining the resource to which the metrics belong + /// + /// Stackdriver Monitored Resource + public static MonitoredResource GetDefaultResource(string projectId) + { + var resource = new MonitoredResource(); + resource.Type = Constants.GLOBAL; + resource.Labels.Add(Constants.PROJECT_ID_LABEL_KEY, projectId); + + // TODO - zeltser - setting monitored resource labels for detected resource + // along with all the other metadata + + return resource; + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Exporter.Stackdriver/Implementation/MetricsConversions.cs b/src/OpenCensus.Exporter.Stackdriver/Implementation/MetricsConversions.cs new file mode 100644 index 000000000..a466f0dc3 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Implementation/MetricsConversions.cs @@ -0,0 +1,336 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Implementation +{ + using Google.Api; + using Google.Cloud.Monitoring.V3; + using Google.Protobuf.WellKnownTypes; + using OpenCensus.Exporter.Stackdriver.Utils; + using OpenCensus.Stats; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using System.Collections.Generic; + using static Google.Api.Distribution.Types; + using static Google.Api.MetricDescriptor.Types; + + /// + /// Conversion methods from Opencensus Stats API to Stackdriver Metrics API + /// + internal static class MetricsConversions + { + /// + /// Converts between OpenCensus aggregation and Stackdriver metric kind + /// + /// Stats Aggregation + /// Stackdriver Metric Kind + public static MetricKind ToMetricKind( + this IAggregation aggregation) + { + return aggregation.Match( + v => MetricKind.Cumulative, // Sum + v => MetricKind.Cumulative, // Count + v => MetricKind.Cumulative, // Mean + v => MetricKind.Cumulative, // Distribution + v => MetricKind.Gauge, // Last value + v => MetricKind.Unspecified); // Default + } + + /// + /// Converts from Opencensus Measure+Aggregation to Stackdriver's ValueType + /// + /// Opencensus Measure definition + /// Opencensus Aggregation definition + /// + public static ValueType ToValueType( + this IMeasure measure, IAggregation aggregation) + { + MetricKind metricKind = aggregation.ToMetricKind(); + if (aggregation is IDistribution && (metricKind == MetricKind.Cumulative || metricKind == MetricKind.Gauge)) + return ValueType.Distribution; + + if (measure is IMeasureDouble && (metricKind == MetricKind.Cumulative || metricKind == MetricKind.Gauge)) + { + return ValueType.Double; + } + + if (measure is IMeasureLong && (metricKind == MetricKind.Cumulative || metricKind == MetricKind.Gauge)) + { + return ValueType.Int64; + } + + // TODO - zeltser - we currently don't support money and string as Opencensus doesn't support them yet + return ValueType.Unspecified; + } + + public static LabelDescriptor ToLabelDescriptor(this ITagKey tagKey) + { + var labelDescriptor = new LabelDescriptor(); + + labelDescriptor.Key = GetStackdriverLabelKey(tagKey.Name); + labelDescriptor.Description = Constants.LABEL_DESCRIPTION; + + // TODO - zeltser - Now we only support string tags + labelDescriptor.ValueType = LabelDescriptor.Types.ValueType.String; + return labelDescriptor; + } + + public static Distribution CreateDistribution( + IDistributionData distributionData, + IBucketBoundaries bucketBoundaries) + { + var bucketOptions = bucketBoundaries.ToBucketOptions(); + var distribution = new Distribution + { + BucketOptions = bucketOptions, + BucketCounts = { CreateBucketCounts(distributionData.BucketCounts) }, + Count = distributionData.Count, + Mean = distributionData.Mean, + SumOfSquaredDeviation = distributionData.SumOfSquaredDeviations, + Range = new Range { Max = distributionData.Max, Min = distributionData.Min } + }; + + return distribution; + } + + /// + /// Creates Stackdriver MetricDescriptor from Opencensus View + /// + /// Metric Descriptor full type name + /// Opencensus View + /// Google Cloud Project Name + /// + /// + /// + public static MetricDescriptor CreateMetricDescriptor( + string metricDescriptorTypeName, + IView view, + ProjectName project, + string domain, + string displayNamePrefix) + { + var metricDescriptor = new MetricDescriptor(); + string viewName = view.Name.AsString; + + metricDescriptor.Name = string.Format($"projects/{project.ProjectId}/metricDescriptors/{metricDescriptorTypeName}"); + metricDescriptor.Type = metricDescriptorTypeName; + metricDescriptor.Description = view.Description; + metricDescriptor.DisplayName = GetDisplayName(viewName, displayNamePrefix); + + foreach (ITagKey tagKey in view.Columns) + { + var labelDescriptor = tagKey.ToLabelDescriptor(); + metricDescriptor.Labels.Add(labelDescriptor); + } + metricDescriptor.Labels.Add( + new LabelDescriptor + { + Key = Constants.OPENCENSUS_TASK, + Description = Constants.OPENCENSUS_TASK_DESCRIPTION, + ValueType = LabelDescriptor.Types.ValueType.String, + }); + + var unit = GetUnit(view.Aggregation, view.Measure); + metricDescriptor.Unit = unit; + metricDescriptor.MetricKind = view.Aggregation.ToMetricKind(); + metricDescriptor.ValueType = view.Measure.ToValueType(view.Aggregation); + + return metricDescriptor; + } + + public static TypedValue CreateTypedValue( + IAggregation aggregation, + IAggregationData aggregationData) + { + return aggregationData.Match( + v => new TypedValue { DoubleValue = v.Sum }, // Double + v => new TypedValue { Int64Value = v.Sum }, // Long + v => new TypedValue { Int64Value = v.Count }, // Count + v => new TypedValue { DoubleValue = v.Count }, // Mean + v => new TypedValue { DistributionValue = CreateDistribution(v, ((IDistribution)aggregation).BucketBoundaries) }, //Distribution + v => new TypedValue { DoubleValue = v.LastValue }, // LastValue Double + v => new TypedValue { Int64Value = v.LastValue }, // LastValue Long + v => new TypedValue { BoolValue = false }); // Default + } + + /// + /// Create a list of counts for Stackdriver from the list of counts in OpenCensus + /// + /// Opencensus list of counts + /// + private static IEnumerable CreateBucketCounts(IReadOnlyList bucketCounts) + { + // The first bucket (underflow bucket) should always be 0 count because the Metrics first bucket + // is [0, first_bound) but Stackdriver distribution consists of an underflow bucket (number 0). + var ret = new List(); + ret.Add(0L); + ret.AddRange(bucketCounts); + return ret; + } + + /// + /// Converts to Stackdriver's + /// + /// + /// + private static BucketOptions ToBucketOptions(this IBucketBoundaries bucketBoundaries) + { + // The first bucket bound should be 0.0 because the Metrics first bucket is + // [0, first_bound) but Stackdriver monitoring bucket bounds begin with -infinity + // (first bucket is (-infinity, 0)) + var bucketOptions = new BucketOptions + { + ExplicitBuckets = new BucketOptions.Types.Explicit + { + Bounds = { 0.0 } + } + }; + bucketOptions.ExplicitBuckets.Bounds.AddRange(bucketBoundaries.Boundaries); + + return bucketOptions; + } + + // Create a Metric using the TagKeys and TagValues. + + /// + /// Generate Stackdriver Metric from Opencensus View + /// + /// + /// + /// Stackdriver Metric Descriptor + /// + /// + public static Metric GetMetric( + IView view, + IReadOnlyList tagValues, + MetricDescriptor metricDescriptor, + string domain) + { + var metric = new Metric(); + metric.Type = metricDescriptor.Type; + + IReadOnlyList columns = view.Columns; + + // Populate metric labels + for (int i = 0; i < tagValues.Count; i++) + { + ITagKey key = columns[i]; + ITagValue value = tagValues[i]; + if (value == null) + { + continue; + } + + string labelKey = GetStackdriverLabelKey(key.Name); + metric.Labels.Add(labelKey, value.AsString); + } + metric.Labels.Add(Constants.OPENCENSUS_TASK, Constants.OPENCENSUS_TASK_VALUE_DEFAULT); + + // TODO - zeltser - make sure all the labels from the metric descriptor were fulfilled + return metric; + } + + /// + /// Convert ViewData to a list of TimeSeries, so that ViewData can be uploaded to Stackdriver. + /// + /// OpenCensus View + /// Stackdriver Metric Descriptor + /// Stackdriver Resource to which the metrics belong + /// The metrics domain (namespace) + /// + public static List CreateTimeSeriesList( + IViewData viewData, + MonitoredResource monitoredResource, + MetricDescriptor metricDescriptor, + string domain) + { + var timeSeriesList = new List(); + if (viewData == null) + { + return timeSeriesList; + } + + IView view = viewData.View; + var startTime = viewData.Start.ToTimestamp(); + + // Each entry in AggregationMap will be converted into an independent TimeSeries object + foreach (var entry in viewData.AggregationMap) + { + var timeSeries = new TimeSeries(); + IReadOnlyList labels = entry.Key.Values; + IAggregationData points = entry.Value; + + timeSeries.Resource = monitoredResource; + timeSeries.ValueType = view.Measure.ToValueType(view.Aggregation); + timeSeries.MetricKind = view.Aggregation.ToMetricKind(); + + timeSeries.Metric = GetMetric(view, labels, metricDescriptor, domain); + + Point point = ExtractPointInInterval(viewData.Start, viewData.End, view.Aggregation, points); + var timeSeriesPoints = new List { point }; + timeSeries.Points.AddRange(timeSeriesPoints); + + timeSeriesList.Add(timeSeries); + } + + return timeSeriesList; + } + + private static Point ExtractPointInInterval( + System.DateTimeOffset startTime, + System.DateTimeOffset endTime, + IAggregation aggregation, + IAggregationData points) + { + return new Point + { + Value = CreateTypedValue(aggregation, points), + Interval = CreateTimeInterval(startTime, endTime) + }; + } + + private static TimeInterval CreateTimeInterval(System.DateTimeOffset start, System.DateTimeOffset end) + { + return new TimeInterval { StartTime = start.ToTimestamp(), EndTime = end.ToTimestamp() }; + } + + internal static string GetUnit(IAggregation aggregation, IMeasure measure) + { + if (aggregation is ICount) + { + return "1"; + } + + return measure.Unit; + } + + internal static string GetDisplayName(string viewName, string displayNamePrefix) + { + return displayNamePrefix + viewName; + } + + /// + /// Creates Stackdriver Label name + /// + /// Opencensus label + /// Label name that complies with Stackdriver label naming rules + internal static string GetStackdriverLabelKey(string label) + { + return label.Replace('/', '_'); + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsConfiguration.cs b/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsConfiguration.cs new file mode 100644 index 000000000..33f5bdbc5 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsConfiguration.cs @@ -0,0 +1,77 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Implementation +{ + using Google.Api; + using Google.Apis.Auth.OAuth2; + using System; + + /// + /// Configuration for exporting stats into Stackdriver + /// + public class StackdriverStatsConfiguration + { + private static readonly TimeSpan DEFAULT_INTERVAL = TimeSpan.FromMinutes(1); + + /// + /// Frequency of the export operation + /// + public TimeSpan ExportInterval { get; set; } + + /// + /// The prefix to append to every OpenCensus metric name in Stackdriver + /// + public string MetricNamePrefix { get; set; } + + /// + /// Google Cloud Project Id + /// + public string ProjectId { get; set; } + + /// + /// Credential used to authenticate against Google Stackdriver Monitoring APIs + /// + public GoogleCredential GoogleCredential { get; set; } + + /// + /// Monitored Resource associated with metrics collection. + /// By default, the exporter detects the environment where the export is happening, + /// such as GKE/AWS/GCE. If the exporter is running on a different environment, + /// monitored resource will be identified as "general". + /// + public MonitoredResource MonitoredResource { get; set; } + + /// + /// Default Stats Configuration for Stackdriver + /// + public static StackdriverStatsConfiguration Default + { + get + { + var defaultConfig = new StackdriverStatsConfiguration + { + ExportInterval = DEFAULT_INTERVAL, + ProjectId = GoogleCloudResourceUtils.GetProjectId(), + MetricNamePrefix = string.Empty, + }; + + defaultConfig.MonitoredResource = GoogleCloudResourceUtils.GetDefaultResource(defaultConfig.ProjectId); + return defaultConfig; + } + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsExporter.cs b/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsExporter.cs new file mode 100644 index 000000000..0f6d17e1a --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverStatsExporter.cs @@ -0,0 +1,368 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Implementation +{ + using Google.Api; + using Google.Api.Gax; + using Google.Api.Gax.Grpc; + using Google.Apis.Auth.OAuth2; + using Google.Cloud.Monitoring.V3; + using Grpc.Auth; + using Grpc.Core; + using OpenCensus.Exporter.Stackdriver.Utils; + using OpenCensus.Stats; + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + + internal class StackdriverStatsExporter + { + private readonly MonitoredResource monitoredResource; + private readonly IViewManager viewManager; + private readonly Dictionary registeredViews = new Dictionary(); + + private readonly Dictionary metricDescriptors = new Dictionary(new ViewNameComparer()); + private readonly ProjectName project; + private readonly GoogleCredential credential; + private MetricServiceClient metricServiceClient; + private CancellationTokenSource tokenSource; + + private const int MAX_BATCH_EXPORT_SIZE = 200; + private const string DEFAULT_DISPLAY_NAME_PREFIX = "OpenCensus/"; + private const string CUSTOM_METRIC_DOMAIN = "custom.googleapis.com/"; + private const string CUSTOM_OPENCENSUS_DOMAIN = CUSTOM_METRIC_DOMAIN + "opencensus/"; + + private const string USER_AGENT_KEY = "user-agent"; + private static string USER_AGENT; + + private readonly string domain; + private readonly string displayNamePrefix; + + private bool isStarted; + + /// + /// Interval between two subsequent stats collection operations + /// + private TimeSpan collectionInterval = TimeSpan.FromMinutes(1); + + /// + /// Interval within which the cancellation should be honored + /// from the point it was requested + /// + private readonly TimeSpan cancellationInterval = TimeSpan.FromSeconds(3); + + private object locker = new object(); + + public StackdriverStatsExporter( + IViewManager viewManager, + StackdriverStatsConfiguration configuration) + { + GaxPreconditions.CheckNotNull(configuration, "configuration"); + GaxPreconditions.CheckNotNull(configuration.MonitoredResource, "configuration.MonitoredResource"); + GaxPreconditions.CheckNotNull(configuration.GoogleCredential, "configuration.GoogleCredential"); + GaxPreconditions.CheckArgument( + configuration.ExportInterval != TimeSpan.Zero, + paramName: "configuration.ExportInterval", + message: "Export interval can't be zero. Typically it's 1 minute"); + + this.viewManager = viewManager; + monitoredResource = configuration.MonitoredResource; + collectionInterval = configuration.ExportInterval; + project = new ProjectName(configuration.ProjectId); + credential = configuration.GoogleCredential; + + domain = GetDomain(configuration.MetricNamePrefix); + displayNamePrefix = GetDisplayNamePrefix(configuration.MetricNamePrefix); + } + + static StackdriverStatsExporter() + { + try + { + string assemblyPackageVersion = typeof(StackdriverStatsExporter).GetTypeInfo().Assembly.GetCustomAttributes().First().InformationalVersion; + USER_AGENT = $"opencensus-csharp/{assemblyPackageVersion}"; + } + catch (Exception) + { + USER_AGENT = $"opencensus-csharp/{Constants.PACKAGE_VERSION_UNDEFINED}"; + } + } + + public void Start() + { + lock (locker) + { + if (!isStarted) + { + tokenSource = new CancellationTokenSource(); + metricServiceClient = CreateMetricServiceClient(credential, tokenSource); + + Task.Factory.StartNew(DoWork, tokenSource.Token); + + isStarted = true; + } + } + } + + public void Stop() + { + lock (locker) + { + if (!isStarted) + { + return; + } + + tokenSource.Cancel(); + } + } + + /// + /// Periodic operation happening on a dedicated thread that is + /// capturing the metrics collected within a collection interval + /// + private void DoWork() + { + try + { + TimeSpan sleepTime = collectionInterval; + var stopWatch = new Stopwatch(); + + while (!tokenSource.IsCancellationRequested) + { + // Calculate the duration of collection iteration + stopWatch.Start(); + + // Collect metrics + Export(); + + stopWatch.Stop(); + + // Adjust the wait time - reduce export operation duration + sleepTime = collectionInterval.Subtract(stopWatch.Elapsed); + sleepTime = sleepTime.Duration(); + + // If the cancellation was requested, we should honor + // that within the cancellation interval, so we wait in + // intervals of + while (sleepTime > cancellationInterval && !tokenSource.IsCancellationRequested) + { + Thread.Sleep(cancellationInterval); + sleepTime = sleepTime.Subtract(cancellationInterval); + } + + Thread.Sleep(sleepTime); + } + } + catch (Exception ex) + { + ExporterStackdriverEventSource.Log.UnknownProblemInWorkerThreadError(ex); + } + } + + private bool RegisterView(IView view) + { + IView existing = null; + if (registeredViews.TryGetValue(view.Name, out existing)) + { + // Ignore views that are already registered. + return existing.Equals(view); + } + registeredViews.Add(view.Name, view); + + string metricDescriptorTypeName = GenerateMetricDescriptorTypeName(view.Name, domain); + + // TODO - zeltser: don't need to create MetricDescriptor for RpcViewConstants once we defined + // canonical metrics. Registration is required only for custom view definitions. Canonical + // views should be pre-registered. + MetricDescriptor metricDescriptor = MetricsConversions.CreateMetricDescriptor( + metricDescriptorTypeName, + view, + project, + domain, + displayNamePrefix); + + if (metricDescriptor == null) + { + // Don't register interval views in this version. + return false; + } + + // Cache metric descriptor and ensure it exists in Stackdriver + if (!metricDescriptors.ContainsKey(view)) + { + metricDescriptors.Add(view, metricDescriptor); + } + return EnsureMetricDescriptorExists(metricDescriptor); + } + + private bool EnsureMetricDescriptorExists(MetricDescriptor metricDescriptor) + { + try + { + var request = new CreateMetricDescriptorRequest(); + request.ProjectName = project; + request.MetricDescriptor = metricDescriptor; + metricServiceClient.CreateMetricDescriptor(request); + } + catch (RpcException e) + { + // Metric already exists + if (e.StatusCode == StatusCode.AlreadyExists) + { + return true; + } + + return false; + } + + return true; + } + + private void Export() + { + var viewDataList = new List(); + foreach (var view in viewManager.AllExportedViews) + { + if (RegisterView(view)) + { + var data = viewManager.GetView(view.Name); + viewDataList.Add(data); + } + } + + // Add time series from all the views that need exporting + var timeSeriesList = new List(); + foreach (var viewData in viewDataList) + { + MetricDescriptor metricDescriptor = metricDescriptors[viewData.View]; + List timeSeries = MetricsConversions.CreateTimeSeriesList(viewData, monitoredResource, metricDescriptor, domain); + timeSeriesList.AddRange(timeSeries); + } + + // Perform the operation in batches of MAX_BATCH_EXPORT_SIZE + foreach (IEnumerable batchedTimeSeries in timeSeriesList.Partition(MAX_BATCH_EXPORT_SIZE)) + { + var request = new CreateTimeSeriesRequest(); + request.ProjectName = project; + request.TimeSeries.AddRange(batchedTimeSeries); + + try + { + metricServiceClient.CreateTimeSeries(request); + } + catch (RpcException e) + { + ExporterStackdriverEventSource.Log.UnknownProblemWhileCreatingStackdriverTimeSeriesError(e); + } + } + } + + private static MetricServiceClient CreateMetricServiceClient(GoogleCredential credential, CancellationTokenSource tokenSource) + { + // Make sure to add Opencensus header to every outgoing call to Stackdriver APIs + Action addOpencensusHeader = m => m.Add(USER_AGENT_KEY, USER_AGENT); + var callSettings = new CallSettings( + cancellationToken: tokenSource.Token, + credentials: null, + timing: null, + headerMutation: addOpencensusHeader, + writeOptions: WriteOptions.Default, + propagationToken: null); + + var channel = new Channel( + MetricServiceClient.DefaultEndpoint.ToString(), + credential.ToChannelCredentials()); + + var metricServiceSettings = new MetricServiceSettings() + { + CallSettings = callSettings + }; + + return MetricServiceClient.Create(channel, settings: metricServiceSettings); + } + + private static string GetDomain(string metricNamePrefix) + { + string domain; + if (string.IsNullOrEmpty(metricNamePrefix)) + { + domain = CUSTOM_OPENCENSUS_DOMAIN; + } + else + { + if (!metricNamePrefix.EndsWith("/")) + { + domain = metricNamePrefix + '/'; + } + else + { + domain = metricNamePrefix; + } + } + return domain; + } + + private string GetDisplayNamePrefix(string metricNamePrefix) + { + if (metricNamePrefix == null) + { + return DEFAULT_DISPLAY_NAME_PREFIX; + } + else + { + if (!metricNamePrefix.EndsWith("/") && !string.IsNullOrEmpty(metricNamePrefix)) + { + metricNamePrefix += '/'; + } + return metricNamePrefix; + } + } + + private static string GenerateMetricDescriptorTypeName(IViewName viewName, string domain) + { + return domain + viewName.AsString; + } + + /// + /// Comparison between two OpenCensus Views + /// + private class ViewNameComparer : IEqualityComparer + { + public bool Equals(IView x, IView y) + { + if (x == null && y == null) + return true; + else if (x == null || y == null) + return false; + else if (x.Name.AsString.Equals(y.Name.AsString)) + return true; + else + return false; + } + + public int GetHashCode(IView obj) + { + return obj.Name.GetHashCode(); + } + } + } +} diff --git a/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverTraceExporter.cs b/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverTraceExporter.cs new file mode 100644 index 000000000..02b0cf4c1 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Implementation/StackdriverTraceExporter.cs @@ -0,0 +1,192 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Implementation +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using Google.Api.Gax.Grpc; + using Google.Cloud.Trace.V2; + using Grpc.Core; + using OpenCensus.Exporter.Stackdriver.Utils; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + + static class SpanExtensions + { + /// + /// Translating to Stackdriver's Span + /// According to specifications + /// + /// Span in OpenCensus format + /// Google Cloud Platform Project Id + /// + public static Span ToSpan(this ISpanData spanData, string projectId) + { + string spanId = spanData.Context.SpanId.ToLowerBase16(); + + // Base span settings + var span = new Span + { + SpanName = new SpanName(projectId, spanData.Context.TraceId.ToLowerBase16(), spanId), + SpanId = spanId, + DisplayName = new TruncatableString { Value = spanData.Name }, + StartTime = spanData.StartTimestamp.ToTimestamp(), + EndTime = spanData.EndTimestamp.ToTimestamp(), + ChildSpanCount = spanData.ChildSpanCount, + }; + if (spanData.ParentSpanId != null) + { + string parentSpanId = spanData.ParentSpanId.ToLowerBase16(); + if (!string.IsNullOrEmpty(parentSpanId)) + { + span.ParentSpanId = parentSpanId; + } + } + + // Span Links + if (spanData.Links != null) + { + span.Links = new Span.Types.Links + { + DroppedLinksCount = spanData.Links.DroppedLinksCount, + Link = { spanData.Links.Links.Select(l => l.ToLink()) } + }; + } + + // Span Attributes + if (spanData.Attributes != null) + { + span.Attributes = new Span.Types.Attributes + { + DroppedAttributesCount = spanData.Attributes != null ? spanData.Attributes.DroppedAttributesCount : 0, + + AttributeMap = { spanData.Attributes?.AttributeMap?.ToDictionary( + s => s.Key, + s => s.Value?.ToAttributeValue()) }, + }; + } + + return span; + } + + public static Span.Types.Link ToLink(this ILink link) + { + var ret = new Span.Types.Link(); + ret.SpanId = link.SpanId.ToLowerBase16(); + ret.TraceId = link.TraceId.ToLowerBase16(); + + if (link.Attributes != null) + { + ret.Attributes = new Span.Types.Attributes + { + + DroppedAttributesCount = OpenCensus.Trace.Config.TraceParams.Default.MaxNumberOfAttributes - link.Attributes.Count, + + AttributeMap = { link.Attributes.ToDictionary( + att => att.Key, + att => att.Value.ToAttributeValue()) } + }; + } + + return ret; + } + + public static Google.Cloud.Trace.V2.AttributeValue ToAttributeValue(this IAttributeValue av) + { + var ret = av.Match( + (s) => new Google.Cloud.Trace.V2.AttributeValue() { StringValue = new TruncatableString() { Value = s } }, + (b) => new Google.Cloud.Trace.V2.AttributeValue() { BoolValue = b }, + (l) => new Google.Cloud.Trace.V2.AttributeValue() { IntValue = l }, + (d) => new Google.Cloud.Trace.V2.AttributeValue() { StringValue = new TruncatableString() { Value = d.ToString() } }, + (obj) => new Google.Cloud.Trace.V2.AttributeValue() { StringValue = new TruncatableString() { Value = obj.ToString() } }); + + return ret; + } + } + + /// + /// Exports a group of spans to Stackdriver + /// + internal class StackdriverTraceExporter : IHandler + { + private static string STACKDRIVER_EXPORTER_VERSION; + private static string OPENCENSUS_EXPORTER_VERSION; + + private readonly Google.Api.Gax.ResourceNames.ProjectName googleCloudProjectId; + private readonly TraceServiceSettings traceServiceSettings; + + public StackdriverTraceExporter(string projectId) + { + googleCloudProjectId = new Google.Api.Gax.ResourceNames.ProjectName(projectId); + + // Set header mutation for every outgoing API call to Stackdriver so the BE knows + // which version of OC client is calling it as well as which version of the exporter + CallSettings callSettings = CallSettings.FromHeaderMutation(StackdriverCallHeaderAppender); + traceServiceSettings = new TraceServiceSettings(); + traceServiceSettings.CallSettings = callSettings; + } + + static StackdriverTraceExporter() + { + try + { + string assemblyPackageVersion = typeof(StackdriverTraceExporter).GetTypeInfo().Assembly.GetCustomAttributes().First().InformationalVersion; + STACKDRIVER_EXPORTER_VERSION = assemblyPackageVersion; + } + catch (Exception) + { + STACKDRIVER_EXPORTER_VERSION = $"{Constants.PACKAGE_VERSION_UNDEFINED}"; + } + + try + { + OPENCENSUS_EXPORTER_VERSION = Assembly.GetCallingAssembly().GetName().Version.ToString(); + } + catch (Exception) + { + OPENCENSUS_EXPORTER_VERSION = $"{Constants.PACKAGE_VERSION_UNDEFINED}"; + } + } + + public async Task ExportAsync(IEnumerable spanDataList) + { + TraceServiceClient traceWriter = TraceServiceClient.Create(settings: traceServiceSettings); + + var batchSpansRequest = new BatchWriteSpansRequest + { + ProjectName = googleCloudProjectId, + Spans = { spanDataList.Select(s => s.ToSpan(googleCloudProjectId.ProjectId)) }, + }; + + await traceWriter.BatchWriteSpansAsync(batchSpansRequest); + } + + /// + /// Appends OpenCensus headers for every outgoing request to Stackdriver Backend + /// + /// The metadata that is sent with every outgoing http request + private static void StackdriverCallHeaderAppender(Metadata metadata) + { + + metadata.Add("AGENT_LABEL_KEY", "g.co/agent"); + metadata.Add("AGENT_LABEL_VALUE_STRING", $"{OPENCENSUS_EXPORTER_VERSION}; stackdriver-exporter {STACKDRIVER_EXPORTER_VERSION}"); + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Exporter.Stackdriver/OpenCensus.Exporter.Stackdriver.csproj b/src/OpenCensus.Exporter.Stackdriver/OpenCensus.Exporter.Stackdriver.csproj new file mode 100644 index 000000000..c6923bbd2 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/OpenCensus.Exporter.Stackdriver.csproj @@ -0,0 +1,31 @@ + + + + + net46;netstandard2.0 + netstandard2.0 + Stackdriver .NET Exporter for OpenCensus. + True + Tracing;OpenCensus;Management;Monitoring;Stackdriver;Google;GCP;distributed-tracing + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + full + true + + + + + + + + + + + + diff --git a/src/OpenCensus.Exporter.Stackdriver/StackdriverExporter.cs b/src/OpenCensus.Exporter.Stackdriver/StackdriverExporter.cs new file mode 100644 index 000000000..61a9a2400 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/StackdriverExporter.cs @@ -0,0 +1,160 @@ + +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver +{ + using Google.Api.Gax; + using Google.Apis.Auth.OAuth2; + using Google.Cloud.Monitoring.V3; + using OpenCensus.Exporter.Stackdriver.Implementation; + using OpenCensus.Stats; + using OpenCensus.Trace.Export; + + /// + /// Implementation of the exporter to Stackdriver + /// + public class StackdriverExporter + { + private const string ExporterName = "StackdriverTraceExporter"; + + private readonly IExportComponent exportComponent; + private readonly IViewManager viewManager; + private readonly string projectId; + private readonly string jsonPath; + private StackdriverStatsExporter statsExporter; + private object locker = new object(); + private bool isInitialized = false; + + /// + /// Initializes a new instance of the class. + /// + /// Google Cloud ProjectId that is used to send data to Stackdriver + /// Exporter to get traces from + /// View manager to get the stats from + public StackdriverExporter( + string projectId, + IExportComponent exportComponent, + IViewManager viewManager) : this(projectId, null, exportComponent, viewManager) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Google Cloud ProjectId that is used to send data to Stackdriver + /// File path to the json file containing the service credential used to authenticate against Stackdriver APIs + /// Exporter to get traces from + /// View manager to get the stats from + public StackdriverExporter( + string projectId, + string jsonPath, + IExportComponent exportComponent, + IViewManager viewManager) + { + GaxPreconditions.CheckNotNullOrEmpty(projectId, "projectId"); + + this.projectId = projectId; + this.jsonPath = jsonPath; + this.exportComponent = exportComponent; + this.viewManager = viewManager; + } + + /// + /// Starts the exporter + /// + public void Start() + { + lock (locker) + { + if (isInitialized) + { + return; + } + + // Register trace exporter + if (exportComponent != null) + { + var traceExporter = new StackdriverTraceExporter(projectId); + exportComponent.SpanExporter.RegisterHandler(ExporterName, traceExporter); + } + + // Register stats(metrics) exporter + if (viewManager != null) + { + GoogleCredential credential = GetGoogleCredential(); + + StackdriverStatsConfiguration statsConfig = StackdriverStatsConfiguration.Default; + statsConfig.GoogleCredential = credential; + if (statsConfig.ProjectId != projectId) + { + statsConfig.ProjectId = projectId; + statsConfig.MonitoredResource = GoogleCloudResourceUtils.GetDefaultResource(projectId); + } + + statsExporter = new StackdriverStatsExporter(viewManager, statsConfig); + statsExporter.Start(); + } + + isInitialized = true; + } + } + + /// + /// Stops the exporter + /// + public void Stop() + { + lock (locker) + { + if (!isInitialized) + { + return; + } + + // Stop tracing exporter + if (exportComponent != null) + { + exportComponent.SpanExporter.UnregisterHandler(ExporterName); + } + + // Stop metrics exporter + if (statsExporter != null) + { + statsExporter.Stop(); + } + + isInitialized = false; + } + } + + private GoogleCredential GetGoogleCredential() + { + GoogleCredential credential; + if (string.IsNullOrEmpty(jsonPath)) + { + credential = GoogleCredential.GetApplicationDefault(); + } + else + { + credential = GoogleCredential.FromFile(jsonPath) + .CreateScoped(MetricServiceClient.DefaultScopes); + } + + return credential; + } + } +} \ No newline at end of file diff --git a/src/OpenCensus.Exporter.Stackdriver/Utils/CommonUtils.cs b/src/OpenCensus.Exporter.Stackdriver/Utils/CommonUtils.cs new file mode 100644 index 000000000..47fbdafff --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Utils/CommonUtils.cs @@ -0,0 +1,55 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Utils +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Common Utility Methods that are not metrics/trace specific + /// + public static class CommonUtils + { + /// + /// Divide the source list into batches of lists of given size. + /// + /// The type of the list + /// The list + /// Size of the batch + /// + public static IEnumerable> Partition(this IEnumerable source, int size) + { + using (var enumerator = source.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + yield return WalkPartition(enumerator, size - 1); + } + } + } + + private static IEnumerable WalkPartition(IEnumerator source, int size) + { + yield return source.Current; + for (int i = 0; i < size && source.MoveNext(); i++) + { + yield return source.Current; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Stackdriver/Utils/ProtoExtensions.cs b/src/OpenCensus.Exporter.Stackdriver/Utils/ProtoExtensions.cs new file mode 100644 index 000000000..43a965590 --- /dev/null +++ b/src/OpenCensus.Exporter.Stackdriver/Utils/ProtoExtensions.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackdriver.Utils +{ + using OpenCensus.Common; + + /// + /// Translation methods from Opencensus structures to common + /// Protobuf structures + /// + public static class ProtoExtensions + { + /// + /// Translates Opencensus Timestamp to Protobuf's timestamp + /// + /// Opencensus timestamp + /// Protobuf's timestamp + public static Google.Protobuf.WellKnownTypes.Timestamp ToTimestamp(this Timestamp timestamp) + { + return new Google.Protobuf.WellKnownTypes.Timestamp { Seconds = timestamp.Seconds, Nanos = timestamp.Nanos }; + } + } +} diff --git a/src/OpenCensus.Exporter.Zipkin/AssemblyInfo.cs b/src/OpenCensus.Exporter.Zipkin/AssemblyInfo.cs new file mode 100644 index 000000000..d4da53303 --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/AssemblyInfo.cs @@ -0,0 +1,19 @@ +// +// Copyright 2018, OpenCensus 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.Runtime.CompilerServices; + +[assembly: System.CLSCompliant(true)] diff --git a/src/OpenCensus.Exporter.Zipkin/Implementation/TraceExporterHandler.cs b/src/OpenCensus.Exporter.Zipkin/Implementation/TraceExporterHandler.cs new file mode 100644 index 000000000..ead09e27f --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/Implementation/TraceExporterHandler.cs @@ -0,0 +1,297 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Zipkin.Implementation +{ + using System; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + using System.Net.Sockets; + using System.Text; + using System.Threading.Tasks; + using Newtonsoft.Json; + using OpenCensus.Common; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + + internal class TraceExporterHandler : IHandler + { + private const string StatusCode = "census.status_code"; + private const string StatusDescription = "census.status_description"; + private const long MillisPerSecond = 1000L; + private const long NanosPerMillisecond = 1000 * 1000; + private const long NanosPerSecond = NanosPerMillisecond * MillisPerSecond; + private readonly ZipkinTraceExporterOptions options; + private readonly ZipkinEndpoint localEndpoint; + private readonly HttpClient httpClient; + + public TraceExporterHandler(ZipkinTraceExporterOptions options, HttpClient client) + { + this.options = options; + this.localEndpoint = this.GetLocalZipkinEndpoint(); + this.httpClient = client ?? new HttpClient(); + } + + public async Task ExportAsync(IEnumerable spanDataList) + { + List zipkinSpans = new List(); + + foreach (var data in spanDataList) + { + var zipkinSpan = this.GenerateSpan(data, this.localEndpoint); + zipkinSpans.Add(zipkinSpan); + } + + await this.SendSpansAsync(zipkinSpans); + } + + internal ZipkinSpan GenerateSpan(ISpanData spanData, ZipkinEndpoint localEndpoint) + { + ISpanContext context = spanData.Context; + long startTimestamp = this.ToEpochMicroseconds(spanData.StartTimestamp); + long endTimestamp = this.ToEpochMicroseconds(spanData.EndTimestamp); + + ZipkinSpan.Builder spanBuilder = + ZipkinSpan.NewBuilder() + .TraceId(this.EncodeTraceId(context.TraceId)) + .Id(this.EncodeSpanId(context.SpanId)) + .Kind(this.ToSpanKind(spanData)) + .Name(spanData.Name) + .Timestamp(this.ToEpochMicroseconds(spanData.StartTimestamp)) + .Duration(endTimestamp - startTimestamp) + .LocalEndpoint(localEndpoint); + + if (spanData.ParentSpanId != null && spanData.ParentSpanId.IsValid) + { + spanBuilder.ParentId(this.EncodeSpanId(spanData.ParentSpanId)); + } + + foreach (var label in spanData.Attributes.AttributeMap) + { + spanBuilder.PutTag(label.Key, this.AttributeValueToString(label.Value)); + } + + Status status = spanData.Status; + + if (status != null) + { + spanBuilder.PutTag(StatusCode, status.CanonicalCode.ToString()); + + if (status.Description != null) + { + spanBuilder.PutTag(StatusDescription, status.Description); + } + } + + foreach (var annotation in spanData.Annotations.Events) + { + spanBuilder.AddAnnotation(this.ToEpochMicroseconds(annotation.Timestamp), annotation.Event.Description); + } + + foreach (var networkEvent in spanData.MessageEvents.Events) + { + spanBuilder.AddAnnotation(this.ToEpochMicroseconds(networkEvent.Timestamp), networkEvent.Event.Type.ToString()); + } + + return spanBuilder.Build(); + } + + private long ToEpochMicroseconds(Timestamp timestamp) + { + long nanos = (timestamp.Seconds * NanosPerSecond) + timestamp.Nanos; + long micros = nanos / 1000L; + return micros; + } + + private string AttributeValueToString(IAttributeValue attributeValue) + { + return attributeValue.Match( + (arg) => { return arg; }, + (arg) => { return arg.ToString(); }, + (arg) => { return arg.ToString(); }, + (arg) => { return arg.ToString(); }, + (arg) => { return null; }); + } + + private string EncodeTraceId(ITraceId traceId) + { + var id = traceId.ToLowerBase16(); + + if (id.Length > 16 && this.options.UseShortTraceIds) + { + id = id.Substring(id.Length - 16, 16); + } + + return id; + } + + private string EncodeSpanId(ISpanId spanId) + { + return spanId.ToLowerBase16(); + } + + private ZipkinSpanKind ToSpanKind(ISpanData spanData) + { + if (spanData.Kind == SpanKind.Server) + { + return ZipkinSpanKind.SERVER; + } + else if (spanData.Kind == SpanKind.Client) + { + if (spanData.HasRemoteParent.HasValue && spanData.HasRemoteParent.Value) + { + return ZipkinSpanKind.SERVER; + } + + return ZipkinSpanKind.CLIENT; + } + + return ZipkinSpanKind.CLIENT; + } + + private async Task SendSpansAsync(IEnumerable spans) + { + try + { + var requestUri = this.options.Endpoint; + var request = this.GetHttpRequestMessage(HttpMethod.Post, requestUri); + request.Content = this.GetRequestContent(spans); + await this.DoPost(this.httpClient, request); + } + catch (Exception) + { + } + } + + private async Task DoPost(HttpClient client, HttpRequestMessage request) + { + try + { + using (HttpResponseMessage response = await client.SendAsync(request)) + { + if (response.StatusCode != HttpStatusCode.OK && + response.StatusCode != HttpStatusCode.Accepted) + { + var statusCode = (int)response.StatusCode; + } + + return; + } + } + catch (Exception) + { + } + } + + private HttpRequestMessage GetHttpRequestMessage(HttpMethod method, Uri requestUri) + { + var request = new HttpRequestMessage(method, requestUri); + + return request; + } + + private HttpContent GetRequestContent(IEnumerable toSerialize) + { + try + { + string json = JsonConvert.SerializeObject(toSerialize); + + return new StringContent(json, Encoding.UTF8, "application/json"); + } + catch (Exception) + { + } + + return new StringContent(string.Empty, Encoding.UTF8, "application/json"); + } + + private ZipkinEndpoint GetLocalZipkinEndpoint() + { + var result = new ZipkinEndpoint() + { + ServiceName = this.options.ServiceName, + }; + + string hostName = this.ResolveHostName(); + + if (!string.IsNullOrEmpty(hostName)) + { + result.Ipv4 = this.ResolveHostAddress(hostName, AddressFamily.InterNetwork); + + result.Ipv6 = this.ResolveHostAddress(hostName, AddressFamily.InterNetworkV6); + } + + return result; + } + + private string ResolveHostAddress(string hostName, AddressFamily family) + { + string result = null; + + try + { + var results = Dns.GetHostAddresses(hostName); + + if (results != null && results.Length > 0) + { + foreach (var addr in results) + { + if (addr.AddressFamily.Equals(family)) + { + var sanitizedAddress = new IPAddress(addr.GetAddressBytes()); // Construct address sans ScopeID + result = sanitizedAddress.ToString(); + + break; + } + } + } + } + catch (Exception) + { + // Ignore + } + + return result; + } + + private string ResolveHostName() + { + string result = null; + + try + { + result = Dns.GetHostName(); + + if (!string.IsNullOrEmpty(result)) + { + var response = Dns.GetHostEntry(result); + + if (response != null) + { + return response.HostName; + } + } + } + catch (Exception) + { + // Ignore + } + + return result; + } + } +} diff --git a/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinAnnotation.cs b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinAnnotation.cs new file mode 100644 index 000000000..e8f0e29ac --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinAnnotation.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Zipkin.Implementation +{ + using Newtonsoft.Json; + + internal class ZipkinAnnotation + { + [JsonProperty("timestamp")] + public long Timestamp { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + } +} diff --git a/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs new file mode 100644 index 000000000..3ce311344 --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinEndpoint.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Zipkin.Implementation +{ + using Newtonsoft.Json; + + internal class ZipkinEndpoint + { + [JsonProperty("serviceName")] + public string ServiceName { get; set; } + + [JsonProperty("ipv4")] + public string Ipv4 { get; set; } + + [JsonProperty("ipv6")] + public string Ipv6 { get; set; } + + [JsonProperty("port")] + public int Port { get; set; } + } +} diff --git a/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpan.cs b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpan.cs new file mode 100644 index 000000000..e01a90592 --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpan.cs @@ -0,0 +1,186 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Zipkin.Implementation +{ + using System; + using System.Collections.Generic; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + internal class ZipkinSpan + { + [JsonProperty("traceId")] + public string TraceId { get; set; } + + [JsonProperty("parentId")] + public string ParentId { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("kind")] + [JsonConverter(typeof(StringEnumConverter))] + public ZipkinSpanKind Kind { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("timestamp")] + public long Timestamp { get; set; } + + [JsonProperty("duration")] + public long Duration { get; set; } + + [JsonProperty("localEndpoint")] + public ZipkinEndpoint LocalEndpoint { get; set; } + + [JsonProperty("remoteEndpoint")] + public ZipkinEndpoint RemoteEndpoint { get; set; } + + [JsonProperty("annotations")] + public List Annotations { get; set; } + + [JsonProperty("tags")] + public Dictionary Tags { get; set; } + + [JsonProperty("debug")] + public bool Debug { get; set; } + + [JsonProperty("shared")] + public bool Shared { get; set; } + + public static Builder NewBuilder() + { + return new Builder(); + } + + public class Builder + { + private readonly ZipkinSpan result = new ZipkinSpan(); + + internal Builder TraceId(string val) + { + this.result.TraceId = val; + return this; + } + + internal Builder Id(string val) + { + this.result.Id = val; + return this; + } + + internal Builder ParentId(string val) + { + this.result.ParentId = val; + return this; + } + + internal Builder Kind(ZipkinSpanKind val) + { + this.result.Kind = val; + return this; + } + + internal Builder Name(string val) + { + this.result.Name = val; + return this; + } + + internal Builder Timestamp(long val) + { + this.result.Timestamp = val; + return this; + } + + internal Builder Duration(long val) + { + this.result.Duration = val; + return this; + } + + internal Builder LocalEndpoint(ZipkinEndpoint val) + { + this.result.LocalEndpoint = val; + return this; + } + + internal Builder RemoteEndpoint(ZipkinEndpoint val) + { + this.result.RemoteEndpoint = val; + return this; + } + + internal Builder Debug(bool val) + { + this.result.Debug = val; + return this; + } + + internal Builder Shared(bool val) + { + this.result.Shared = val; + return this; + } + + internal Builder PutTag(string key, string value) + { + if (this.result.Tags == null) + { + this.result.Tags = new Dictionary(); + } + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + this.result.Tags[key] = value ?? throw new ArgumentNullException(nameof(value)); + + return this; + } + + internal Builder AddAnnotation(long timestamp, string value) + { + if (this.result.Annotations == null) + { + this.result.Annotations = new List(2); + } + + this.result.Annotations.Add(new ZipkinAnnotation() { Timestamp = timestamp, Value = value }); + + return this; + } + + internal ZipkinSpan Build() + { + if (this.result.TraceId == null) + { + throw new ArgumentException("Trace ID should not be null"); + } + + if (this.result.Id == null) + { + throw new ArgumentException("ID should not be null"); + } + + return this.result; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpanKind.cs b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpanKind.cs new file mode 100644 index 000000000..ed37462b6 --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/Implementation/ZipkinSpanKind.cs @@ -0,0 +1,41 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Zipkin.Implementation +{ + internal enum ZipkinSpanKind + { + /// + /// Client span. + /// + CLIENT, + + /// + /// Server span. + /// + SERVER, + + /// + /// Producer. + /// + PRODUCER, + + /// + /// Consumer. + /// + CONSUMER, + } +} diff --git a/src/OpenCensus.Exporter.Zipkin/OpenCensus.Exporter.Zipkin.csproj b/src/OpenCensus.Exporter.Zipkin/OpenCensus.Exporter.Zipkin.csproj new file mode 100644 index 000000000..381bd3f2f --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/OpenCensus.Exporter.Zipkin.csproj @@ -0,0 +1,31 @@ + + + + + net46;netstandard2.0 + netstandard2.0 + True + + + + Zipkin exporter for OpenCensus + Tracing;OpenCensus;Management;Monitoring;Zipkin;distributed-tracing + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + + + All + + + + + + + + diff --git a/src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporter.cs b/src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporter.cs new file mode 100644 index 000000000..aa2627253 --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporter.cs @@ -0,0 +1,95 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Zipkin +{ + using System.Net.Http; + using OpenCensus.Exporter.Zipkin.Implementation; + using OpenCensus.Trace.Export; + + /// + /// Exporter of Open Census traces to Zipkin. + /// + public class ZipkinTraceExporter + { + private const string ExporterName = "ZipkinTraceExporter"; + + private readonly ZipkinTraceExporterOptions options; + + private readonly IExportComponent exportComponent; + + private readonly object lck = new object(); + + private readonly HttpClient httpClient; + + private TraceExporterHandler handler; + + /// + /// Initializes a new instance of the class. + /// This exporter sends Open Census traces to Zipkin. + /// + /// Zipkin exporter configuration options. + /// Exporter to get traces from. + /// Http client to use to upload telemetry. + /// For local development with invalid certificates use code like this: + /// new HttpClient(new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }). + /// + public ZipkinTraceExporter(ZipkinTraceExporterOptions options, IExportComponent exportComponent, HttpClient client = null) + { + this.options = options; + + this.exportComponent = exportComponent; + + this.httpClient = client; + } + + /// + /// Start exporter. + /// + public void Start() + { + lock (this.lck) + { + if (this.handler != null) + { + return; + } + + this.handler = new TraceExporterHandler(this.options, this.httpClient); + + this.exportComponent.SpanExporter.RegisterHandler(ExporterName, this.handler); + } + } + + /// + /// Stop exporter. + /// + public void Stop() + { + lock (this.lck) + { + if (this.handler == null) + { + return; + } + + this.exportComponent.SpanExporter.UnregisterHandler(ExporterName); + + this.handler = null; + } + } + } +} diff --git a/src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporterOptions.cs b/src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporterOptions.cs new file mode 100644 index 000000000..15c2bc74f --- /dev/null +++ b/src/OpenCensus.Exporter.Zipkin/ZipkinTraceExporterOptions.cs @@ -0,0 +1,47 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Zipkin +{ + using System; + + /// + /// Zipkin trace exporter options. + /// + public sealed class ZipkinTraceExporterOptions + { + /// + /// Gets or sets Zipkin endpoint address. See https://zipkin.io/zipkin-api/#/default/post_spans. + /// Typically https://zipkin-server-name:9411/api/v2/spans. + /// + public Uri Endpoint { get; set; } = new Uri("http://localhost:9411/api/v2/spans"); + + /// + /// Gets or sets timeout in seconds. + /// + public TimeSpan TimeoutSeconds { get; set; } = TimeSpan.FromSeconds(10); + + /// + /// Gets or sets the name of the service reporting telemetry. + /// + public string ServiceName { get; set; } = "Open Census Exporter"; + + /// + /// Gets or sets a value indicating whether short trace id should be used. + /// + public bool UseShortTraceIds { get; set; } = false; + } +} diff --git a/src/OpenCensus/Implementation/Constants.cs b/src/OpenCensus/Implementation/Constants.cs new file mode 100644 index 000000000..c2f091435 --- /dev/null +++ b/src/OpenCensus/Implementation/Constants.cs @@ -0,0 +1,44 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Implementation +{ + /// + /// Constants enforced by OpenCensus specifications. + /// + internal class Constants + { + /// + /// Maximum length of the resource type name. + /// + public const int MaxResourceTypeNameLength = 255; + + /// + /// Special resource type name that is assigned if nothing else is detected. + /// + public const string GlobalResourceType = "Global"; + + /// + /// OpenCensus Resource Type Environment Variable Name. + /// + public const string ResourceTypeEnvironmentVariable = "OC_RESOURCE_TYPE"; + + /// + /// OpenCensus Resource Labels Environment Variable Name. + /// + public const string ResourceLabelsEnvironmentVariable = "OC_RESOURCE_LABELS"; + } +} diff --git a/src/OpenCensus/Implementation/OpenCensusEventSource.cs b/src/OpenCensus/Implementation/OpenCensusEventSource.cs new file mode 100644 index 000000000..125168070 --- /dev/null +++ b/src/OpenCensus/Implementation/OpenCensusEventSource.cs @@ -0,0 +1,84 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Implementation +{ + using System; + using System.Diagnostics.Tracing; + using System.Globalization; + using System.Threading; + + [EventSource(Name = "OpenCensus-Base")] + internal class OpenCensusEventSource : EventSource + { + public static readonly OpenCensusEventSource Log = new OpenCensusEventSource(); + + [NonEvent] + public void ExporterThrownExceptionWarning(Exception ex) + { + if (Log.IsEnabled(EventLevel.Warning, EventKeywords.All)) + { + this.ExporterThrownExceptionWarning(ToInvariantString(ex)); + } + } + + [Event(1, Message = "Exporter failed to export items. Exception: {0}", Level = EventLevel.Warning)] + public void ExporterThrownExceptionWarning(string ex) + { + this.WriteEvent(1, ex); + } + + [Event(2, Message = "Failed to parse a resource tag. {0} should be an ASCII string with a length greater than 0 and not exceeding {1} characters.", Level = EventLevel.Warning)] + public void InvalidCharactersInResourceElement(string element) + { + this.WriteEvent(2, element, Constants.MaxResourceTypeNameLength); + } + + [NonEvent] + public void FailedReadingEnvironmentVariableWarning(string environmentVariableName, Exception ex) + { + if (Log.IsEnabled(EventLevel.Warning, EventKeywords.All)) + { + this.FailedReadingEnvironmentVariableWarning(environmentVariableName, ToInvariantString(ex)); + } + } + + [Event(3, Message = "Failed to read environment variable {0}. Main library failed with security exception: {1}", Level = EventLevel.Warning)] + public void FailedReadingEnvironmentVariableWarning(string environmentVariableName, string ex) + { + this.WriteEvent(3, environmentVariableName, ex); + } + + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + private static string ToInvariantString(Exception exception) + { + CultureInfo originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/src/OpenCensus/Internal/NoopScope.cs b/src/OpenCensus/Internal/NoopScope.cs new file mode 100644 index 000000000..cc51c73be --- /dev/null +++ b/src/OpenCensus/Internal/NoopScope.cs @@ -0,0 +1,41 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Internal +{ + using OpenCensus.Common; + + public sealed class NoopScope : IScope + { + public static readonly IScope NoopInstance = new NoopScope(); + + private NoopScope() + { + } + + public static IScope Instance + { + get + { + return NoopInstance; + } + } + + public void Dispose() + { + } + } +} diff --git a/src/OpenCensus/Internal/SimpleEventQueue.cs b/src/OpenCensus/Internal/SimpleEventQueue.cs new file mode 100644 index 000000000..fdcb3225a --- /dev/null +++ b/src/OpenCensus/Internal/SimpleEventQueue.cs @@ -0,0 +1,26 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Internal +{ + public class SimpleEventQueue : IEventQueue + { + public void Enqueue(IEventQueueEntry entry) + { + entry.Process(); + } + } +} diff --git a/src/OpenCensus/Internal/VarInt.cs b/src/OpenCensus/Internal/VarInt.cs new file mode 100644 index 000000000..beb06bff3 --- /dev/null +++ b/src/OpenCensus/Internal/VarInt.cs @@ -0,0 +1,277 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Internal +{ + using System; + using System.IO; + + public static class VarInt + { + /** Maximum encoded size of 32-bit positive integers (in bytes) */ + public const int MaxVarintSize = 5; + + /** maximum encoded size of 64-bit longs, and negative 32-bit ints (in bytes) */ + public const int MaxVarlongSize = 10; + + public static int VarIntSize(int i) + { + int result = 0; + uint ui = (uint)i; + do + { + result++; + ui = ui >> 7; + } + while (ui != 0); + return result; + } + + public static int GetVarInt(byte[] src, int offset, int[] dst) + { + int result = 0; + int shift = 0; + int b; + do + { + if (shift >= 32) + { + // Out of range + throw new ArgumentOutOfRangeException("varint too long"); + } + + // Get 7 bits from next byte + b = src[offset++]; + result |= (b & 0x7F) << shift; + shift += 7; + } + while ((b & 0x80) != 0); + dst[0] = result; + return offset; + } + + public static int PutVarInt(int v, byte[] sink, int offset) + { + uint uv = (uint)v; + do + { + // Encode next 7 bits + terminator bit + uint bits = uv & 0x7F; + uv >>= 7; + byte b = (byte)(bits + ((uv != 0) ? 0x80 : 0)); + sink[offset++] = b; + } + while (uv != 0); + return offset; + } + + // public static int getVarInt(ByteBuffer src) + // { + // int tmp; + // if ((tmp = src.get()) >= 0) + // { + // return tmp; + // } + // int result = tmp & 0x7f; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 7; + // } + // else + // { + // result |= (tmp & 0x7f) << 7; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 14; + // } + // else + // { + // result |= (tmp & 0x7f) << 14; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 21; + // } + // else + // { + // result |= (tmp & 0x7f) << 21; + // result |= (tmp = src.get()) << 28; + // while (tmp < 0) + // { + // // We get into this loop only in the case of overflow. + // // By doing this, we can call getVarInt() instead of + // // getVarLong() when we only need an int. + // tmp = src.get(); + // } + // } + // } + // } + // return result; + // } + + // public static void putVarInt(int v, ByteBuffer sink) + // { + // while (true) + // { + // int bits = v & 0x7f; + // v >>>= 7; + // if (v == 0) + // { + // sink.put((byte)bits); + // return; + // } + // sink.put((byte)(bits | 0x80)); + // } + // } + + /** + * Reads a varint from the given InputStream and returns the decoded value as an int. + * + * @param inputStream the InputStream to read from + */ + public static int GetVarInt(Stream inputStream) + { + int result = 0; + int shift = 0; + int b; + do + { + if (shift >= 32) + { + // Out of range + throw new ArgumentOutOfRangeException("varint too long"); + } + + // Get 7 bits from next byte + b = inputStream.ReadByte(); + result |= (b & 0x7F) << shift; + shift += 7; + } + while ((b & 0x80) != 0); + return result; + } + + public static void PutVarInt(int v, Stream outputStream) + { + byte[] bytes = new byte[VarIntSize(v)]; + PutVarInt(v, bytes, 0); + outputStream.Write(bytes, 0, bytes.Length); + } + + public static int VarLongSize(long v) + { + int result = 0; + ulong uv = (ulong)v; + do + { + result++; + uv >>= 7; + } + while (uv != 0); + return result; + } + + // public static long GetVarLong(ByteBuffer src) + // { + // long tmp; + // if ((tmp = src.get()) >= 0) + // { + // return tmp; + // } + // long result = tmp & 0x7f; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 7; + // } + // else + // { + // result |= (tmp & 0x7f) << 7; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 14; + // } + // else + // { + // result |= (tmp & 0x7f) << 14; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 21; + // } + // else + // { + // result |= (tmp & 0x7f) << 21; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 28; + // } + // else + // { + // result |= (tmp & 0x7f) << 28; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 35; + // } + // else + // { + // result |= (tmp & 0x7f) << 35; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 42; + // } + // else + // { + // result |= (tmp & 0x7f) << 42; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 49; + // } + // else + // { + // result |= (tmp & 0x7f) << 49; + // if ((tmp = src.get()) >= 0) + // { + // result |= tmp << 56; + // } + // else + // { + // result |= (tmp & 0x7f) << 56; + // result |= ((long)src.get()) << 63; + // } + // } + // } + // } + // } + // } + // } + // } + // return result; + // } + + // public static void PutVarLong(long v, ByteBuffer sink) + // { + // while (true) + // { + // int bits = ((int)v) & 0x7f; + // v >>>= 7; + // if (v == 0) + // { + // sink.put((byte)bits); + // return; + // } + // sink.put((byte)(bits | 0x80)); + // } + // } + } +} diff --git a/src/OpenCensus/OpenCensus.csproj b/src/OpenCensus/OpenCensus.csproj new file mode 100644 index 000000000..330d53b82 --- /dev/null +++ b/src/OpenCensus/OpenCensus.csproj @@ -0,0 +1,39 @@ + + + + + net46;netstandard2.0 + netstandard2.0 + OpenCensus .NET API + True + Tracing;OpenCensus;Management;Monitoring + https://opencensus.io/images/opencensus-logo.png + https://opencensus.io + Apache-2.0 + OpenCensus authors + true + + + + full + true + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenCensus.sln'))/build/OpenCensus.prod.loose.ruleset + + + + + + + + All + + + + + + + + diff --git a/src/OpenCensus/Properties/AssemblyInfo.cs b/src/OpenCensus/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..85a7dd2fa --- /dev/null +++ b/src/OpenCensus/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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.Runtime.CompilerServices; + +[assembly: System.CLSCompliant(true)] + +[assembly: InternalsVisibleTo("OpenCensus.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenCensus.Collector.Dependencies.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenCensus.Collector.AspNetCore.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenCensus.Collector.StackExchangeRedis.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif \ No newline at end of file diff --git a/src/OpenCensus/Resources/Resource.cs b/src/OpenCensus/Resources/Resource.cs new file mode 100644 index 000000000..3588c0126 --- /dev/null +++ b/src/OpenCensus/Resources/Resource.cs @@ -0,0 +1,187 @@ +// +// Copyright 2018, OpenCensus 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 theLicense 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. +// + +namespace OpenCensus.Resources +{ + using System; + using System.Collections.Generic; + using System.Security; + using System.Text.RegularExpressions; + using OpenCensus.Implementation; + using OpenCensus.Tags; + using OpenCensus.Utils; + + /// + /// Represents a resource that captures identification information about the entities for which signals (stats or traces) + /// are reported. It further provides a framework for detection of resource information from the environment and progressive + /// population as signals propagate from the core instrumentation library to a backend's exporter. + /// + public abstract class Resource : IResource + { + /// + /// Tag list splitter. + /// + private const char LabelListSplitter = ','; + + /// + /// Key-value splitter. + /// + private const char LabelKeyValueSplitter = '='; + + /// + /// Environment identification (for example, AKS/GKE/etc). + /// + private static readonly string EnvironmentType; + + private static readonly ITag[] EnvironmentToLabelMap; + + static Resource() + { + string openCensusResourceType; + string openCensusEnvironmentTags; + + try + { + openCensusResourceType = Environment.GetEnvironmentVariable(Constants.ResourceTypeEnvironmentVariable); + } + catch (SecurityException ex) + { + openCensusResourceType = Constants.GlobalResourceType; + + Log.FailedReadingEnvironmentVariableWarning(Constants.ResourceTypeEnvironmentVariable, ex); + } + + try + { + openCensusEnvironmentTags = Environment.GetEnvironmentVariable(Constants.ResourceLabelsEnvironmentVariable); + } + catch (SecurityException ex) + { + openCensusEnvironmentTags = string.Empty; + + Log.FailedReadingEnvironmentVariableWarning(Constants.ResourceLabelsEnvironmentVariable, ex); + } + + TryParseResourceType(openCensusResourceType, out EnvironmentType); + EnvironmentToLabelMap = ParseResourceLabels(Environment.GetEnvironmentVariable(Constants.ResourceLabelsEnvironmentVariable)); + } + + /// + /// Gets or sets the identification of the resource. + /// + public abstract string Type { get; protected set; } + + /// + /// Gets the map between the tag and its value. + /// + public abstract IEnumerable Tags { get; } + + private static OpenCensusEventSource Log => OpenCensusEventSource.Log; + + /// + /// Creates a label/tag map from the OC_RESOURCE_LABELS environment variable. + /// OC_RESOURCE_LABELS: A comma-separated list of labels describing the source in more detail, + /// e.g. “key1=val1,key2=val2”. Domain names and paths are accepted as label keys. + /// Values may be quoted or unquoted in general. If a value contains whitespaces, =, or " characters, it must + /// always be quoted. + /// + /// Environment tags as a raw, comma separated string + /// Environment Tags as a list. + internal static ITag[] ParseResourceLabels(string rawEnvironmentTags) + { + if (rawEnvironmentTags == null) + { + return new ITag[0] { }; + } + else + { + var labels = new List(); + string[] rawLabels = rawEnvironmentTags.Split(LabelListSplitter); + + Regex regex = new Regex("^\"|\"$", RegexOptions.Compiled); + + foreach (var rawLabel in rawLabels) + { + string[] keyValuePair = rawLabel.Split(LabelKeyValueSplitter); + if (keyValuePair.Length != 2) + { + continue; + } + + string key = keyValuePair[0].Trim(); + string value = Regex.Replace(keyValuePair[1].Trim(), "^\"|\"$", string.Empty); + + if (!IsValidAndNotEmpty(key)) + { + Log.InvalidCharactersInResourceElement("Label key"); + return new ITag[0] { }; + } + + if (!IsValid(value)) + { + Log.InvalidCharactersInResourceElement("Label key"); + return new ITag[0] { }; + } + + labels.Add(new Tag(TagKey.Create(key), TagValue.Create(value))); + } + + return labels.ToArray(); + } + } + + internal static bool TryParseResourceType(string rawEnvironmentType, out string resourceType) + { + if (string.IsNullOrEmpty(rawEnvironmentType)) + { + resourceType = Constants.GlobalResourceType; + return false; + } + + if (rawEnvironmentType.Length > Constants.MaxResourceTypeNameLength) + { + Log.InvalidCharactersInResourceElement(rawEnvironmentType); + resourceType = Constants.GlobalResourceType; + return false; + } + + resourceType = rawEnvironmentType.Trim(); + return true; + } + + /// + /// Checks whether given string is a valid printable ASCII string with a length not exeeding + /// characters. + /// + /// The string. + /// Whether given string is valid. + private static bool IsValid(string name) + { + return name.Length <= Constants.MaxResourceTypeNameLength && StringUtil.IsPrintableString(name); + } + + /// + /// Checks whether given string is a valid printable ASCII string with a length + /// greater than 0 and not exceeding characters. + /// + /// The string. + /// Whether given string is valid. + private static bool IsValidAndNotEmpty(string name) + { + return !string.IsNullOrEmpty(name) && IsValid(name); + } + } +} diff --git a/src/OpenCensus/Stats/Aggregation.cs b/src/OpenCensus/Stats/Aggregation.cs new file mode 100644 index 000000000..9ca5f6668 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregation.cs @@ -0,0 +1,26 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Aggregations; + + public abstract class Aggregation : IAggregation + { + public abstract T Match(Func p0, Func p1, Func p2, Func p3, Func p4, Func p5); + } +} diff --git a/src/OpenCensus/Stats/AggregationData.cs b/src/OpenCensus/Stats/AggregationData.cs new file mode 100644 index 000000000..0a8c8e6ee --- /dev/null +++ b/src/OpenCensus/Stats/AggregationData.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Aggregations; + + public abstract class AggregationData : IAggregationData + { + public abstract T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction); + } +} diff --git a/src/OpenCensus/Stats/Aggregations/Count.cs b/src/OpenCensus/Stats/Aggregations/Count.cs new file mode 100644 index 000000000..6d909d53f --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/Count.cs @@ -0,0 +1,69 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public sealed class Count : Aggregation, ICount + { + private static readonly Count Instance = new Count(); + + private Count() + { + } + + public static ICount Create() + { + return Instance; + } + + public override T Match(Func p0, Func p1, Func p2, Func p3, Func p4, Func p5) + { + return p1.Invoke(this); + } + + /// + public override string ToString() + { + return "Count{" + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Count) + { + return true; + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + return h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/CountData.cs b/src/OpenCensus/Stats/Aggregations/CountData.cs new file mode 100644 index 000000000..f826f73c9 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/CountData.cs @@ -0,0 +1,81 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public class CountData : AggregationData, ICountData + { + internal CountData(long count) + { + this.Count = count; + } + + public long Count { get; } + + public static ICountData Create(long count) + { + return new CountData(count); + } + + public override T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction) + { + return p2.Invoke(this); + } + + /// + public override string ToString() + { + return "CountData{" + + "count=" + this.Count + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is CountData that) + { + return this.Count == that.Count; + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (this.Count >> 32) ^ this.Count; + return (int)h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/Distribution.cs b/src/OpenCensus/Stats/Aggregations/Distribution.cs new file mode 100644 index 000000000..a775b70f3 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/Distribution.cs @@ -0,0 +1,82 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public sealed class Distribution : Aggregation, IDistribution + { + private Distribution() + { + } + + private Distribution(IBucketBoundaries bucketBoundaries) + { + this.BucketBoundaries = bucketBoundaries ?? throw new ArgumentNullException("Null bucketBoundaries"); + } + + public IBucketBoundaries BucketBoundaries { get; } + + public static IDistribution Create(IBucketBoundaries bucketBoundaries) + { + if (bucketBoundaries == null) + { + throw new ArgumentNullException(nameof(bucketBoundaries)); + } + + return new Distribution(bucketBoundaries); + } + + public override T Match(Func p0, Func p1, Func p2, Func p3, Func p4, Func p5) + { + return p3.Invoke(this); + } + + /// + public override string ToString() + { + return "Distribution{" + + "bucketBoundaries=" + this.BucketBoundaries + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Distribution that) + { + return this.BucketBoundaries.Equals(that.BucketBoundaries); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.BucketBoundaries.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/DistributionData.cs b/src/OpenCensus/Stats/Aggregations/DistributionData.cs new file mode 100644 index 000000000..c70c86372 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/DistributionData.cs @@ -0,0 +1,135 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Utils; + + public class DistributionData : AggregationData, IDistributionData + { + internal DistributionData(double mean, long count, double min, double max, double sumOfSquaredDeviations, IReadOnlyList bucketCounts) + { + this.Mean = mean; + this.Count = count; + this.Min = min; + this.Max = max; + this.SumOfSquaredDeviations = sumOfSquaredDeviations; + this.BucketCounts = bucketCounts ?? throw new ArgumentNullException(nameof(bucketCounts)); + } + + public double Mean { get; } + + public long Count { get; } + + public double Min { get; } + + public double Max { get; } + + public double SumOfSquaredDeviations { get; } + + public IReadOnlyList BucketCounts { get; } + + public static IDistributionData Create(double mean, long count, double min, double max, double sumOfSquaredDeviations, IReadOnlyList bucketCounts) + { + if (!double.IsPositiveInfinity(min) || !double.IsNegativeInfinity(max)) + { + if (!(min <= max)) + { + throw new ArgumentOutOfRangeException("max should be greater or equal to min."); + } + } + + if (bucketCounts == null) + { + throw new ArgumentNullException(nameof(bucketCounts)); + } + + IReadOnlyList bucketCountsCopy = new List(bucketCounts).AsReadOnly(); + + return new DistributionData( + mean, count, min, max, sumOfSquaredDeviations, bucketCountsCopy); + } + + public override T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction) + { + return p4.Invoke(this); + } + + /// + public override string ToString() + { + return "DistributionData{" + + "mean=" + this.Mean + ", " + + "count=" + this.Count + ", " + + "min=" + this.Min + ", " + + "max=" + this.Max + ", " + + "sumOfSquaredDeviations=" + this.SumOfSquaredDeviations + ", " + + "bucketCounts=" + Collections.ToString(this.BucketCounts) + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is DistributionData that) + { + return (DoubleUtil.ToInt64(this.Mean) == DoubleUtil.ToInt64(that.Mean)) + && (this.Count == that.Count) + && (DoubleUtil.ToInt64(this.Min) == DoubleUtil.ToInt64(that.Min)) + && (DoubleUtil.ToInt64(this.Max) == DoubleUtil.ToInt64(that.Max)) + && (DoubleUtil.ToInt64(this.SumOfSquaredDeviations) == DoubleUtil.ToInt64(that.SumOfSquaredDeviations)) + && this.BucketCounts.SequenceEqual(that.BucketCounts); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Mean) >> 32) ^ DoubleUtil.ToInt64(this.Mean); + h *= 1000003; + h ^= (this.Count >> 32) ^ this.Count; + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Min) >> 32) ^ DoubleUtil.ToInt64(this.Min); + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Max) >> 32) ^ DoubleUtil.ToInt64(this.Max); + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.SumOfSquaredDeviations) >> 32) ^ DoubleUtil.ToInt64(this.SumOfSquaredDeviations); + h *= 1000003; + h ^= this.BucketCounts.GetHashCode(); + return (int)h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/LastValue.cs b/src/OpenCensus/Stats/Aggregations/LastValue.cs new file mode 100644 index 000000000..f3d3db379 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/LastValue.cs @@ -0,0 +1,69 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public sealed class LastValue : Aggregation, ILastValue + { + private static readonly LastValue Instance = new LastValue(); + + private LastValue() + { + } + + public static ILastValue Create() + { + return Instance; + } + + public override T Match(Func p0, Func p1, Func p2, Func p3, Func p4, Func p5) + { + return p4.Invoke(this); + } + + /// + public override string ToString() + { + return "LastValue{" + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is LastValue) + { + return true; + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + return h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/LastValueDataDouble.cs b/src/OpenCensus/Stats/Aggregations/LastValueDataDouble.cs new file mode 100644 index 000000000..709f102de --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/LastValueDataDouble.cs @@ -0,0 +1,86 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + using OpenCensus.Utils; + + public sealed class LastValueDataDouble : AggregationData, ILastValueDataDouble + { + private LastValueDataDouble() + { + } + + private LastValueDataDouble(double lastValue) + { + this.LastValue = lastValue; + } + + public double LastValue { get; } + + public static ILastValueDataDouble Create(double lastValue) + { + return new LastValueDataDouble(lastValue); + } + + /// + public override string ToString() + { + return "LastValueDataDouble{" + + "lastValue=" + this.LastValue + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is LastValueDataDouble that) + { + return DoubleUtil.ToInt64(this.LastValue) == DoubleUtil.ToInt64(that.LastValue); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.LastValue) >> 32) ^ DoubleUtil.ToInt64(this.LastValue); + return (int)h; + } + + public override T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction) + { + return p5.Invoke(this); + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/LastValueDataLong.cs b/src/OpenCensus/Stats/Aggregations/LastValueDataLong.cs new file mode 100644 index 000000000..4b6c61edc --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/LastValueDataLong.cs @@ -0,0 +1,85 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public sealed class LastValueDataLong : AggregationData, ILastValueDataLong + { + private LastValueDataLong() + { + } + + private LastValueDataLong(long lastValue) + { + this.LastValue = lastValue; + } + + public long LastValue { get; } + + public static ILastValueDataLong Create(long lastValue) + { + return new LastValueDataLong(lastValue); + } + + /// + public override string ToString() + { + return "LastValueDataLong{" + + "lastValue=" + this.LastValue + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is LastValueDataLong that) + { + return this.LastValue == that.LastValue; + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (this.LastValue >> 32) ^ this.LastValue; + return (int)h; + } + + public override T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction) + { + return p6.Invoke(this); + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/Mean.cs b/src/OpenCensus/Stats/Aggregations/Mean.cs new file mode 100644 index 000000000..b17f6eb25 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/Mean.cs @@ -0,0 +1,69 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public sealed class Mean : Aggregation, IMean + { + private static readonly Mean Instance = new Mean(); + + internal Mean() + { + } + + public static IMean Create() + { + return Instance; + } + + public override T Match(Func p0, Func p1, Func p2, Func p3, Func p4, Func p5) + { + return p2.Invoke(this); + } + + /// + public override string ToString() + { + return "Mean{" + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Mean) + { + return true; + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + return h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/MeanData.cs b/src/OpenCensus/Stats/Aggregations/MeanData.cs new file mode 100644 index 000000000..d0c4e2510 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/MeanData.cs @@ -0,0 +1,103 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + using OpenCensus.Utils; + + public class MeanData : AggregationData, IMeanData + { + internal MeanData(double mean, long count, double min, double max) + { + this.Mean = mean; + this.Count = count; + this.Min = min; + this.Max = max; + } + + public double Mean { get; } + + public long Count { get; } + + public double Max { get; } + + public double Min { get; } + + public static IMeanData Create(double mean, long count, double min, double max) + { + return new MeanData(mean, count, min, max); + } + + public override T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction) + { + return p3.Invoke(this); + } + + /// + public override string ToString() + { + return "MeanData{" + + "mean=" + this.Mean + ", " + + "min=" + this.Min + ", " + + "max=" + this.Max + ", " + + "count=" + this.Count + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is MeanData that) + { + return (DoubleUtil.ToInt64(this.Mean) == DoubleUtil.ToInt64(that.Mean)) + && (this.Count == that.Count) + && (DoubleUtil.ToInt64(this.Min) == DoubleUtil.ToInt64(that.Min)) + && (DoubleUtil.ToInt64(this.Max) == DoubleUtil.ToInt64(that.Max)); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Mean) >> 32) ^ DoubleUtil.ToInt64(this.Mean); + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Min) >> 32) ^ DoubleUtil.ToInt64(this.Min); + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Max) >> 32) ^ DoubleUtil.ToInt64(this.Max); + h *= 1000003; + h ^= (this.Count >> 32) ^ this.Count; + return (int)h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/Sum.cs b/src/OpenCensus/Stats/Aggregations/Sum.cs new file mode 100644 index 000000000..a2f6c4896 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/Sum.cs @@ -0,0 +1,69 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public sealed class Sum : Aggregation, ISum + { + private static readonly Sum Instance = new Sum(); + + internal Sum() + { + } + + public static ISum Create() + { + return Instance; + } + + public override T Match(Func p0, Func p1, Func p2, Func p3, Func p4, Func p5) + { + return p0.Invoke(this); + } + + /// + public override string ToString() + { + return "Sum{" + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Sum) + { + return true; + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + return h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/SumDataDouble.cs b/src/OpenCensus/Stats/Aggregations/SumDataDouble.cs new file mode 100644 index 000000000..b0042b317 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/SumDataDouble.cs @@ -0,0 +1,82 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + using OpenCensus.Utils; + + public sealed class SumDataDouble : AggregationData, ISumDataDouble + { + internal SumDataDouble(double sum) + { + this.Sum = sum; + } + + public double Sum { get; } + + public static ISumDataDouble Create(double sum) + { + return new SumDataDouble(sum); + } + + public override T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction) + { + return p0.Invoke(this); + } + + /// + public override string ToString() + { + return "SumDataDouble{" + + "sum=" + this.Sum + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is SumDataDouble that) + { + return DoubleUtil.ToInt64(this.Sum) == DoubleUtil.ToInt64(that.Sum); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Sum) >> 32) ^ DoubleUtil.ToInt64(this.Sum); + return (int)h; + } + } +} diff --git a/src/OpenCensus/Stats/Aggregations/SumDataLong.cs b/src/OpenCensus/Stats/Aggregations/SumDataLong.cs new file mode 100644 index 000000000..eeac4f203 --- /dev/null +++ b/src/OpenCensus/Stats/Aggregations/SumDataLong.cs @@ -0,0 +1,81 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Aggregations +{ + using System; + + public sealed class SumDataLong : AggregationData, ISumDataLong + { + internal SumDataLong(long sum) + { + this.Sum = sum; + } + + public long Sum { get; } + + public static ISumDataLong Create(long sum) + { + return new SumDataLong(sum); + } + + public override T Match( + Func p0, + Func p1, + Func p2, + Func p3, + Func p4, + Func p5, + Func p6, + Func defaultFunction) + { + return p1.Invoke(this); + } + + /// + public override string ToString() + { + return "SumDataLong{" + + "sum=" + this.Sum + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is SumDataLong that) + { + return this.Sum == that.Sum; + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= (this.Sum >> 32) ^ this.Sum; + return (int)h; + } + } +} diff --git a/src/OpenCensus/Stats/BucketBoundaries.cs b/src/OpenCensus/Stats/BucketBoundaries.cs new file mode 100644 index 000000000..2974ded6b --- /dev/null +++ b/src/OpenCensus/Stats/BucketBoundaries.cs @@ -0,0 +1,93 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Utils; + + public sealed class BucketBoundaries : IBucketBoundaries + { + private BucketBoundaries(IReadOnlyList boundaries) + { + this.Boundaries = boundaries; + } + + public IReadOnlyList Boundaries { get; } + + public static IBucketBoundaries Create(IEnumerable bucketBoundaries) + { + if (bucketBoundaries == null) + { + throw new ArgumentNullException(nameof(bucketBoundaries)); + } + + List bucketBoundariesCopy = new List(bucketBoundaries); + + if (bucketBoundariesCopy.Count > 1) + { + double lower = bucketBoundariesCopy[0]; + for (int i = 1; i < bucketBoundariesCopy.Count; i++) + { + double next = bucketBoundariesCopy[i]; + if (!(lower < next)) + { + throw new ArgumentOutOfRangeException("Bucket boundaries not sorted."); + } + + lower = next; + } + } + + return new BucketBoundaries(bucketBoundariesCopy.AsReadOnly()); + } + + /// + public override string ToString() + { + return "BucketBoundaries{" + + "boundaries=" + Collections.ToString(this.Boundaries) + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is BucketBoundaries that) + { + return this.Boundaries.SequenceEqual(that.Boundaries); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Boundaries.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Stats/CumulativeMutableViewData.cs b/src/OpenCensus/Stats/CumulativeMutableViewData.cs new file mode 100644 index 000000000..1da0e90c3 --- /dev/null +++ b/src/OpenCensus/Stats/CumulativeMutableViewData.cs @@ -0,0 +1,77 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using OpenCensus.Tags; + + internal class CumulativeMutableViewData : MutableViewData + { + private readonly IDictionary tagValueAggregationMap = new Dictionary(); + private DateTimeOffset start; + + internal CumulativeMutableViewData(IView view, DateTimeOffset start) + : base(view) + { + this.start = start; + } + + internal override void Record(ITagContext context, double value, DateTimeOffset timestamp) + { + var values = GetTagValues(GetTagMap(context), this.View.Columns); + var tagValues = TagValues.Create(values); + if (!this.tagValueAggregationMap.ContainsKey(tagValues)) + { + this.tagValueAggregationMap.Add(tagValues, CreateMutableAggregation(this.View.Aggregation)); + } + + this.tagValueAggregationMap[tagValues].Add(value); + } + + internal override IViewData ToViewData(DateTimeOffset now, StatsCollectionState state) + { + if (state == StatsCollectionState.ENABLED) + { + return ViewData.Create( + this.View, + CreateAggregationMap(this.tagValueAggregationMap, this.View.Measure), + this.start, + now); + } + else + { + // If Stats state is DISABLED, return an empty ViewData. + return ViewData.Create( + this.View, + new Dictionary(), + DateTimeOffset.MinValue, + DateTimeOffset.MinValue); + } + } + + internal override void ClearStats() + { + this.tagValueAggregationMap.Clear(); + } + + internal override void ResumeStatsCollection(DateTimeOffset now) + { + this.start = now; + } + } +} diff --git a/src/OpenCensus/Stats/CurrentStatsState.cs b/src/OpenCensus/Stats/CurrentStatsState.cs new file mode 100644 index 000000000..e17e10e3f --- /dev/null +++ b/src/OpenCensus/Stats/CurrentStatsState.cs @@ -0,0 +1,74 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + public sealed class CurrentStatsState + { + private readonly object lck = new object(); + private StatsCollectionState currentState = StatsCollectionState.ENABLED; + private bool isRead; + + public StatsCollectionState Value + { + get + { + lock (this.lck) + { + this.isRead = true; + return this.Internal; + } + } + + set + { + } + } + + internal StatsCollectionState Internal + { + get + { + return this.currentState; + } + } + + // Sets current state to the given state. Returns true if the current state is changed, false + // otherwise. + internal bool Set(StatsCollectionState state) + { + lock (this.lck) + { + if (this.isRead) + { + throw new ArgumentException("State was already read, cannot set state."); + } + + if (state == this.currentState) + { + return false; + } + else + { + this.currentState = state; + return true; + } + } + } + } +} diff --git a/src/OpenCensus/Stats/Measure.cs b/src/OpenCensus/Stats/Measure.cs new file mode 100644 index 000000000..be3c66a17 --- /dev/null +++ b/src/OpenCensus/Stats/Measure.cs @@ -0,0 +1,34 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Measures; + + public abstract class Measure : IMeasure + { + internal const int NameMaxLength = 255; + + public abstract string Name { get; } + + public abstract string Description { get; } + + public abstract string Unit { get; } + + public abstract T Match(Func p0, Func p1, Func defaultFunction); + } +} diff --git a/src/OpenCensus/Stats/MeasureMap.cs b/src/OpenCensus/Stats/MeasureMap.cs new file mode 100644 index 000000000..f081957c5 --- /dev/null +++ b/src/OpenCensus/Stats/MeasureMap.cs @@ -0,0 +1,60 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + + internal sealed class MeasureMap : MeasureMapBase + { + private readonly StatsManager statsManager; + private readonly MeasureMapBuilder builder = MeasureMapBuilder.Builder(); + + internal MeasureMap(StatsManager statsManager) + { + this.statsManager = statsManager; + } + + public override IMeasureMap Put(IMeasureDouble measure, double value) + { + this.builder.Put(measure, value); + return this; + } + + public override IMeasureMap Put(IMeasureLong measure, long value) + { + this.builder.Put(measure, value); + return this; + } + + public override void Record() + { + // Use the context key directly, to avoid depending on the tags implementation. + this.Record(CurrentTagContextUtils.CurrentTagContext); + } + + public override void Record(ITagContext tags) + { + this.statsManager.Record(tags, this.builder.Build()); + } + + internal static IMeasureMap Create(StatsManager statsManager) + { + return new MeasureMap(statsManager); + } + } +} diff --git a/src/OpenCensus/Stats/MeasureMapBase.cs b/src/OpenCensus/Stats/MeasureMapBase.cs new file mode 100644 index 000000000..c889a92de --- /dev/null +++ b/src/OpenCensus/Stats/MeasureMapBase.cs @@ -0,0 +1,32 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + + public abstract class MeasureMapBase : IMeasureMap + { + public abstract IMeasureMap Put(IMeasureDouble measure, double value); + + public abstract IMeasureMap Put(IMeasureLong measure, long value); + + public abstract void Record(); + + public abstract void Record(ITagContext tags); + } +} diff --git a/src/OpenCensus/Stats/MeasureMapBuilder.cs b/src/OpenCensus/Stats/MeasureMapBuilder.cs new file mode 100644 index 000000000..d17e6cc07 --- /dev/null +++ b/src/OpenCensus/Stats/MeasureMapBuilder.cs @@ -0,0 +1,64 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System.Collections.Generic; + using OpenCensus.Stats.Measurements; + using OpenCensus.Stats.Measures; + + internal class MeasureMapBuilder + { + private readonly List measurements = new List(); + + internal static MeasureMapBuilder Builder() + { + return new MeasureMapBuilder(); + } + + internal MeasureMapBuilder Put(IMeasureDouble measure, double value) + { + this.measurements.Add(MeasurementDouble.Create(measure, value)); + return this; + } + + internal MeasureMapBuilder Put(IMeasureLong measure, long value) + { + this.measurements.Add(MeasurementLong.Create(measure, value)); + return this; + } + + internal IEnumerable Build() + { + // Note: this makes adding measurements quadratic but is fastest for the sizes of + // MeasureMapInternals that we should see. We may want to go to a strategy of sort/eliminate + // for larger MeasureMapInternals. + for (int i = this.measurements.Count - 1; i >= 0; i--) + { + for (int j = i - 1; j >= 0; j--) + { + if (this.measurements[i].Measure == this.measurements[j].Measure) + { + this.measurements.RemoveAt(j); + j--; + } + } + } + + return new List(this.measurements); + } + } +} diff --git a/src/OpenCensus/Stats/MeasureToViewMap.cs b/src/OpenCensus/Stats/MeasureToViewMap.cs new file mode 100644 index 000000000..f78621743 --- /dev/null +++ b/src/OpenCensus/Stats/MeasureToViewMap.cs @@ -0,0 +1,222 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using OpenCensus.Tags; + + internal sealed class MeasureToViewMap + { + private readonly object lck = new object(); + private readonly IDictionary> mutableMap = new Dictionary>(); + + private readonly IDictionary registeredViews = new Dictionary(); + + // TODO(songya): consider adding a Measure.Name class + private readonly IDictionary registeredMeasures = new Dictionary(); + + // Cached set of exported views. It must be set to null whenever a view is registered or + // unregistered. + private volatile ISet exportedViews; + + /// + /// Gets a {@link ViewData} corresponding to the given {@link View.Name}. + /// + internal ISet ExportedViews + { + get + { + ISet views = this.exportedViews; + if (views == null) + { + lock (this.lck) + { + this.exportedViews = views = FilterExportedViews(this.registeredViews.Values); + } + } + + return views; + } + } + + internal IViewData GetView(IViewName viewName, StatsCollectionState state) + { + lock (this.lck) + { + MutableViewData view = this.GetMutableViewData(viewName); + return view?.ToViewData(DateTimeOffset.Now, state); + } + } + + /** Enable stats collection for the given {@link View}. */ + internal void RegisterView(IView view) + { + lock (this.lck) + { + this.exportedViews = null; + this.registeredViews.TryGetValue(view.Name, out IView existing); + if (existing != null) + { + if (existing.Equals(view)) + { + // Ignore views that are already registered. + return; + } + else + { + throw new ArgumentException("A different view with the same name is already registered: " + existing); + } + } + + IMeasure measure = view.Measure; + this.registeredMeasures.TryGetValue(measure.Name, out IMeasure registeredMeasure); + if (registeredMeasure != null && !registeredMeasure.Equals(measure)) + { + throw new ArgumentException("A different measure with the same name is already registered: " + registeredMeasure); + } + + this.registeredViews.Add(view.Name, view); + if (registeredMeasure == null) + { + this.registeredMeasures.Add(measure.Name, measure); + } + + this.AddMutableViewData(view.Measure.Name, MutableViewData.Create(view, DateTimeOffset.Now)); + } + } + + // Records stats with a set of tags. + internal void Record(ITagContext tags, IEnumerable stats, DateTimeOffset timestamp) + { + lock (this.lck) + { + foreach (var measurement in stats) + { + IMeasure measure = measurement.Measure; + this.registeredMeasures.TryGetValue(measure.Name, out IMeasure value); + if (!measure.Equals(value)) + { + // unregistered measures will be ignored. + continue; + } + + var views = this.mutableMap[measure.Name]; + foreach (MutableViewData view in views) + { + measurement.Match( + (arg) => + { + view.Record(tags, arg.Value, timestamp); + return null; + }, + (arg) => + { + view.Record(tags, arg.Value, timestamp); + return null; + }, + (arg) => + { + throw new ArgumentException(); + }); + } + } + } + } + + // Clear stats for all the current MutableViewData + internal void ClearStats() + { + lock (this.lck) + { + foreach (var entry in this.mutableMap) + { + foreach (MutableViewData mutableViewData in entry.Value) + { + mutableViewData.ClearStats(); + } + } + } + } + + // Resume stats collection for all MutableViewData. + internal void ResumeStatsCollection(DateTimeOffset now) + { + lock (this.lck) + { + foreach (var entry in this.mutableMap) + { + foreach (MutableViewData mutableViewData in entry.Value) + { + mutableViewData.ResumeStatsCollection(now); + } + } + } + } + + // Returns the subset of the given views that should be exported. + private static ISet FilterExportedViews(ICollection allViews) + { + return ImmutableHashSet.CreateRange(allViews); + } + + private void AddMutableViewData(string name, MutableViewData mutableViewData) + { + if (this.mutableMap.ContainsKey(name)) + { + this.mutableMap[name].Add(mutableViewData); + } + else + { + this.mutableMap.Add(name, new List() { mutableViewData }); + } + } + + private MutableViewData GetMutableViewData(IViewName viewName) + { + lock (this.lck) + { + this.registeredViews.TryGetValue(viewName, out IView view); + if (view == null) + { + return null; + } + + this.mutableMap.TryGetValue(view.Measure.Name, out ICollection views); + if (views != null) + { + foreach (MutableViewData viewData in views) + { + if (viewData.View.Name.Equals(viewName)) + { + return viewData; + } + } + } + + throw new InvalidOperationException( + "Internal error: Not recording stats for view: \"" + + viewName + + "\" registeredViews=" + + this.registeredViews + + ", mutableMap=" + + this.mutableMap); + } + } + } +} diff --git a/src/OpenCensus/Stats/Measurement.cs b/src/OpenCensus/Stats/Measurement.cs new file mode 100644 index 000000000..3db138311 --- /dev/null +++ b/src/OpenCensus/Stats/Measurement.cs @@ -0,0 +1,28 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Measurements; + + public abstract class Measurement : IMeasurement + { + public abstract IMeasure Measure { get; } + + public abstract T Match(Func p0, Func p1, Func defaultFunction); + } +} diff --git a/src/OpenCensus/Stats/Measurements/MeasurementDouble.cs b/src/OpenCensus/Stats/Measurements/MeasurementDouble.cs new file mode 100644 index 000000000..f86848a2f --- /dev/null +++ b/src/OpenCensus/Stats/Measurements/MeasurementDouble.cs @@ -0,0 +1,82 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measurements +{ + using System; + using OpenCensus.Stats.Measures; + using OpenCensus.Utils; + + public sealed class MeasurementDouble : Measurement, IMeasurementDouble + { + internal MeasurementDouble(IMeasureDouble measure, double value) + { + this.Measure = measure ?? throw new ArgumentNullException(nameof(measure)); + this.Value = value; + } + + public override IMeasure Measure { get; } + + public double Value { get; } + + public static IMeasurementDouble Create(IMeasureDouble measure, double value) + { + return new MeasurementDouble(measure, value); + } + + public override T Match(Func p0, Func p1, Func defaultFunction) + { + return p0.Invoke(this); + } + + /// + public override string ToString() + { + return "MeasurementDouble{" + + "measure=" + this.Measure + ", " + + "value=" + this.Value + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is MeasurementDouble that) + { + return this.Measure.Equals(that.Measure) + && (DoubleUtil.ToInt64(this.Value) == DoubleUtil.ToInt64(that.Value)); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= this.Measure.GetHashCode(); + h *= 1000003; + h ^= (DoubleUtil.ToInt64(this.Value) >> 32) ^ DoubleUtil.ToInt64(this.Value); + return (int)h; + } + } +} diff --git a/src/OpenCensus/Stats/Measurements/MeasurementLong.cs b/src/OpenCensus/Stats/Measurements/MeasurementLong.cs new file mode 100644 index 000000000..0bafebb8d --- /dev/null +++ b/src/OpenCensus/Stats/Measurements/MeasurementLong.cs @@ -0,0 +1,81 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measurements +{ + using System; + using OpenCensus.Stats.Measures; + + public sealed class MeasurementLong : Measurement, IMeasurementLong + { + private MeasurementLong(IMeasureLong measure, long value) + { + this.Measure = measure ?? throw new ArgumentNullException(nameof(measure)); + this.Value = value; + } + + public override IMeasure Measure { get; } + + public long Value { get; } + + public static IMeasurementLong Create(IMeasureLong measure, long value) + { + return new MeasurementLong(measure, value); + } + + public override T Match(Func p0, Func p1, Func defaultFunction) + { + return p1.Invoke(this); + } + + /// + public override string ToString() + { + return "MeasurementLong{" + + "measure=" + this.Measure + ", " + + "value=" + this.Value + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is MeasurementLong that) + { + return this.Measure.Equals(that.Measure) + && (this.Value == that.Value); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= this.Measure.GetHashCode(); + h *= 1000003; + h ^= (this.Value >> 32) ^ this.Value; + return (int)h; + } + } +} diff --git a/src/OpenCensus/Stats/Measures/MeasureDouble.cs b/src/OpenCensus/Stats/Measures/MeasureDouble.cs new file mode 100644 index 000000000..0c2772fb7 --- /dev/null +++ b/src/OpenCensus/Stats/Measures/MeasureDouble.cs @@ -0,0 +1,96 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measures +{ + using System; + using OpenCensus.Utils; + + public class MeasureDouble : Measure, IMeasureDouble + { + internal MeasureDouble(string name, string description, string unit) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + this.Unit = unit ?? throw new ArgumentNullException(nameof(unit)); + } + + public override string Name { get; } + + public override string Description { get; } + + public override string Unit { get; } + + public static IMeasureDouble Create(string name, string description, string unit) + { + if (!(StringUtil.IsPrintableString(name) && name.Length <= NameMaxLength)) + { + throw new ArgumentOutOfRangeException( + "Name should be a ASCII string with a length no greater than " + + NameMaxLength + + " characters."); + } + + return new MeasureDouble(name, description, unit); + } + + public override T Match(Func p0, Func p1, Func defaultFunction) + { + return p0.Invoke(this); + } + + /// + public override string ToString() + { + return "MeasureDouble{" + + "name=" + this.Name + ", " + + "description=" + this.Description + ", " + + "unit=" + this.Unit + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is MeasureDouble that) + { + return this.Name.Equals(that.Name) + && this.Description.Equals(that.Description) + && this.Unit.Equals(that.Unit); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Name.GetHashCode(); + h *= 1000003; + h ^= this.Description.GetHashCode(); + h *= 1000003; + h ^= this.Unit.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Stats/Measures/MeasureLong.cs b/src/OpenCensus/Stats/Measures/MeasureLong.cs new file mode 100644 index 000000000..eb97f0184 --- /dev/null +++ b/src/OpenCensus/Stats/Measures/MeasureLong.cs @@ -0,0 +1,96 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Measures +{ + using System; + using OpenCensus.Utils; + + public sealed class MeasureLong : Measure, IMeasureLong + { + internal MeasureLong(string name, string description, string unit) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + this.Unit = unit ?? throw new ArgumentNullException(nameof(unit)); + } + + public override string Name { get; } + + public override string Description { get; } + + public override string Unit { get; } + + public static IMeasureLong Create(string name, string description, string unit) + { + if (!(StringUtil.IsPrintableString(name) && name.Length <= NameMaxLength)) + { + throw new ArgumentOutOfRangeException( + "Name should be a ASCII string with a length no greater than " + + NameMaxLength + + " characters."); + } + + return new MeasureLong(name, description, unit); + } + + public override T Match(Func p0, Func p1, Func defaultFunction) + { + return p1.Invoke(this); + } + + /// + public override string ToString() + { + return "MeasureLong{" + + "name=" + this.Name + ", " + + "description=" + this.Description + ", " + + "unit=" + this.Unit + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is MeasureLong that) + { + return this.Name.Equals(that.Name) + && this.Description.Equals(that.Description) + && this.Unit.Equals(that.Unit); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Name.GetHashCode(); + h *= 1000003; + h ^= this.Description.GetHashCode(); + h *= 1000003; + h ^= this.Unit.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Stats/MutableAggregation.cs b/src/OpenCensus/Stats/MutableAggregation.cs new file mode 100644 index 000000000..4d10e9d94 --- /dev/null +++ b/src/OpenCensus/Stats/MutableAggregation.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + internal abstract class MutableAggregation + { + private const double Tolerance = 1e-6; + + protected MutableAggregation() + { + } + + // Tolerance for double comparison. + internal abstract void Add(double value); + + internal abstract void Combine(MutableAggregation other, double fraction); + + internal abstract T Match(Func p0, Func p1, Func p2, Func p3, Func p4); + } +} diff --git a/src/OpenCensus/Stats/MutableCount.cs b/src/OpenCensus/Stats/MutableCount.cs new file mode 100644 index 000000000..017b79b83 --- /dev/null +++ b/src/OpenCensus/Stats/MutableCount.cs @@ -0,0 +1,56 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + internal sealed class MutableCount : MutableAggregation + { + internal MutableCount() + { + } + + internal long Count { get; private set; } = 0; + + internal static MutableCount Create() + { + return new MutableCount(); + } + + internal override void Add(double value) + { + this.Count++; + } + + internal override void Combine(MutableAggregation other, double fraction) + { + if (!(other is MutableCount mutable)) + { + throw new ArgumentException("MutableCount expected."); + } + + var result = fraction * mutable.Count; + long rounded = (long)Math.Round(result); + this.Count += rounded; + } + + internal override T Match(Func p0, Func p1, Func p2, Func p3, Func p4) + { + return p1.Invoke(this); + } + } +} diff --git a/src/OpenCensus/Stats/MutableDistribution.cs b/src/OpenCensus/Stats/MutableDistribution.cs new file mode 100644 index 000000000..228688747 --- /dev/null +++ b/src/OpenCensus/Stats/MutableDistribution.cs @@ -0,0 +1,156 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + internal sealed class MutableDistribution : MutableAggregation + { + private const double TOLERANCE = 1e-6; + + internal MutableDistribution(IBucketBoundaries bucketBoundaries) + { + this.BucketBoundaries = bucketBoundaries; + this.BucketCounts = new long[bucketBoundaries.Boundaries.Count + 1]; + } + + internal double Sum { get; set; } = 0.0; + + internal double Mean { get; set; } = 0.0; + + internal long Count { get; set; } = 0; + + internal double SumOfSquaredDeviations { get; set; } = 0.0; + + // Initial "impossible" values, that will get reset as soon as first value is added. + internal double Min { get; set; } = double.PositiveInfinity; + + internal double Max { get; set; } = double.NegativeInfinity; + + internal IBucketBoundaries BucketBoundaries { get; } + + internal long[] BucketCounts { get; } + + internal static MutableDistribution Create(IBucketBoundaries bucketBoundaries) + { + if (bucketBoundaries == null) + { + throw new ArgumentNullException(nameof(bucketBoundaries)); + } + + return new MutableDistribution(bucketBoundaries); + } + + internal override void Add(double value) + { + this.Sum += value; + this.Count++; + + /* + * Update the sum of squared deviations from the mean with the given value. For values + * x_i this is Sum[i=1..n]((x_i - mean)^2) + * + * Computed using Welfords method (see + * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance, or Knuth, "The Art of + * Computer Programming", Vol. 2, page 323, 3rd edition) + */ + double deltaFromMean = value - this.Mean; + this.Mean += deltaFromMean / this.Count; + double deltaFromMean2 = value - this.Mean; + this.SumOfSquaredDeviations += deltaFromMean * deltaFromMean2; + + if (value < this.Min) + { + this.Min = value; + } + + if (value > this.Max) + { + this.Max = value; + } + + for (int i = 0; i < this.BucketBoundaries.Boundaries.Count; i++) + { + if (value < this.BucketBoundaries.Boundaries[i]) + { + this.BucketCounts[i]++; + return; + } + } + + this.BucketCounts[this.BucketCounts.Length - 1]++; + } + + // We don't compute fractional MutableDistribution, it's either whole or none. + internal override void Combine(MutableAggregation other, double fraction) + { + if (!(other is MutableDistribution mutableDistribution)) + { + throw new ArgumentException("MutableDistribution expected."); + } + + if (Math.Abs(1.0 - fraction) > TOLERANCE) + { + return; + } + + if (!this.BucketBoundaries.Equals(mutableDistribution.BucketBoundaries)) + { + throw new ArgumentException("Bucket boundaries should match."); + } + + // Algorithm for calculating the combination of sum of squared deviations: + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm. + if (this.Count + mutableDistribution.Count > 0) + { + double delta = mutableDistribution.Mean - this.Mean; + this.SumOfSquaredDeviations = + this.SumOfSquaredDeviations + + mutableDistribution.SumOfSquaredDeviations + + (Math.Pow(delta, 2) + * this.Count + * mutableDistribution.Count + / (this.Count + mutableDistribution.Count)); + } + + this.Count += mutableDistribution.Count; + this.Sum += mutableDistribution.Sum; + this.Mean = this.Sum / this.Count; + + if (mutableDistribution.Min < this.Min) + { + this.Min = mutableDistribution.Min; + } + + if (mutableDistribution.Max > this.Max) + { + this.Max = mutableDistribution.Max; + } + + long[] bucketCounts = mutableDistribution.BucketCounts; + for (int i = 0; i < bucketCounts.Length; i++) + { + this.BucketCounts[i] += bucketCounts[i]; + } + } + + internal override T Match(Func p0, Func p1, Func p2, Func p3, Func p4) + { + return p3.Invoke(this); + } + } +} diff --git a/src/OpenCensus/Stats/MutableLastValue.cs b/src/OpenCensus/Stats/MutableLastValue.cs new file mode 100644 index 000000000..04f6090c7 --- /dev/null +++ b/src/OpenCensus/Stats/MutableLastValue.cs @@ -0,0 +1,67 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + internal sealed class MutableLastValue : MutableAggregation + { + internal double LastValue = double.NaN; + + // TODO(songya): remove this once interval stats is completely removed. + internal bool Initialized = false; + + internal MutableLastValue() + { + } + + internal static MutableLastValue Create() + { + return new MutableLastValue(); + } + + internal override void Add(double value) + { + this.LastValue = value; + + // TODO(songya): remove this once interval stats is completely removed. + if (!this.Initialized) + { + this.Initialized = true; + } + } + + internal override void Combine(MutableAggregation other, double fraction) + { + if (!(other is MutableLastValue mutable)) + { + throw new ArgumentException("MutableLastValue expected."); + } + + MutableLastValue otherValue = (MutableLastValue)other; + + // Assume other is always newer than this, because we combined interval buckets in time order. + // If there's a newer value, overwrite current value. + this.LastValue = otherValue.Initialized ? otherValue.LastValue : this.LastValue; + } + + internal override T Match(Func p0, Func p1, Func p2, Func p3, Func p4) + { + return p4.Invoke(this); + } + } +} diff --git a/src/OpenCensus/Stats/MutableMean.cs b/src/OpenCensus/Stats/MutableMean.cs new file mode 100644 index 000000000..af4c2ca78 --- /dev/null +++ b/src/OpenCensus/Stats/MutableMean.cs @@ -0,0 +1,92 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + internal sealed class MutableMean : MutableAggregation + { + internal MutableMean() + { + } + + internal double Sum { get; set; } = 0.0; + + internal long Count { get; set; } = 0; + + internal double Mean + { + get + { + return this.Count == 0 ? 0 : this.Sum / this.Count; + } + } + + internal double Min { get; set; } = double.MaxValue; + + internal double Max { get; set; } = double.MinValue; + + internal static MutableMean Create() + { + return new MutableMean(); + } + + internal override void Add(double value) + { + this.Count++; + this.Sum += value; + if (value < this.Min) + { + this.Min = value; + } + + if (value > this.Max) + { + this.Max = value; + } + } + + internal override void Combine(MutableAggregation other, double fraction) + { + if (!(other is MutableMean mutable)) + { + throw new ArgumentException("MutableMean expected."); + } + + var result = fraction * mutable.Count; + long rounded = (long)Math.Round(result); + this.Count += rounded; + + this.Sum += mutable.Sum * fraction; + + if (mutable.Min < this.Min) + { + this.Min = mutable.Min; + } + + if (mutable.Max > this.Max) + { + this.Max = mutable.Max; + } + } + + internal override T Match(Func p0, Func p1, Func p2, Func p3, Func p4) + { + return p2.Invoke(this); + } + } +} diff --git a/src/OpenCensus/Stats/MutableSum.cs b/src/OpenCensus/Stats/MutableSum.cs new file mode 100644 index 000000000..d46d11417 --- /dev/null +++ b/src/OpenCensus/Stats/MutableSum.cs @@ -0,0 +1,54 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + internal sealed class MutableSum : MutableAggregation + { + internal MutableSum() + { + } + + internal double Sum { get; private set; } = 0.0; + + internal static MutableSum Create() + { + return new MutableSum(); + } + + internal override void Add(double value) + { + this.Sum += value; + } + + internal override void Combine(MutableAggregation other, double fraction) + { + if (!(other is MutableSum mutable)) + { + throw new ArgumentException("MutableSum expected."); + } + + this.Sum += fraction * mutable.Sum; + } + + internal override T Match(Func p0, Func p1, Func p2, Func p3, Func p4) + { + return p0.Invoke(this); + } + } +} diff --git a/src/OpenCensus/Stats/MutableViewData.cs b/src/OpenCensus/Stats/MutableViewData.cs new file mode 100644 index 000000000..edcbc68c4 --- /dev/null +++ b/src/OpenCensus/Stats/MutableViewData.cs @@ -0,0 +1,215 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Tags; + + internal abstract class MutableViewData + { + internal static readonly ITagValue UnknownTagValue = null; + + internal static readonly Timestamp ZeroTimestamp = Timestamp.Create(0, 0); + + private const long MillisPerSecond = 1000L; + private const long NanosPerMilli = 1000 * 1000; + + protected MutableViewData(IView view) + { + this.View = view; + } + + internal IView View { get; } + + private static Func CreateMutableSum { get; } = (s) => { return MutableSum.Create(); }; + + private static Func CreateMutableCount { get; } = (s) => { return MutableCount.Create(); }; + + private static Func CreateMutableMean { get; } = (s) => { return MutableMean.Create(); }; + + private static Func CreateMutableLastValue { get; } = (s) => { return MutableLastValue.Create(); }; + + private static Func CreateMutableDistribution { get; } = (s) => { return MutableDistribution.Create(s.BucketBoundaries); }; + + private static Func ThrowArgumentException { get; } = (s) => { throw new ArgumentException(); }; + + private static Func CreateCountData { get; } = (s) => { return CountData.Create(s.Count); }; + + private static Func CreateMeanData { get; } = (s) => { return MeanData.Create(s.Mean, s.Count, s.Min, s.Max); }; + + private static Func CreateDistributionData { get; } = (s) => + { + List boxedBucketCounts = new List(); + foreach (long bucketCount in s.BucketCounts) + { + boxedBucketCounts.Add(bucketCount); + } + + return DistributionData.Create( + s.Mean, + s.Count, + s.Min, + s.Max, + s.SumOfSquaredDeviations, + boxedBucketCounts); + }; + + internal static IDictionary GetTagMap(ITagContext ctx) + { + if (ctx is TagContext) + { + return ((TagContext)ctx).Tags; + } + else + { + IDictionary tags = new Dictionary(); + foreach (var tag in ctx) + { + tags.Add(tag.Key, tag.Value); + } + + return tags; + } + } + + internal static IReadOnlyList GetTagValues(IDictionary tags, IReadOnlyList columns) + { + List tagValues = new List(columns.Count); + + // Record all the measures in a "Greedy" way. + // Every view aggregates every measure. This is similar to doing a GROUPBY view’s keys. + for (int i = 0; i < columns.Count; ++i) + { + ITagKey tagKey = columns[i]; + if (!tags.ContainsKey(tagKey)) + { + // replace not found key values by null. + tagValues.Add(UnknownTagValue); + } + else + { + tagValues.Add(tags[tagKey]); + } + } + + return tagValues.AsReadOnly(); + } + + // Returns the milliseconds representation of a Duration. + internal static long ToMillis(Duration duration) + { + return (duration.Seconds * MillisPerSecond) + (duration.Nanos / NanosPerMilli); + } + + internal static MutableAggregation CreateMutableAggregation(IAggregation aggregation) + { + return aggregation.Match( + CreateMutableSum, + CreateMutableCount, + CreateMutableMean, + CreateMutableDistribution, + CreateMutableLastValue, + ThrowArgumentException); + } + + internal static IAggregationData CreateAggregationData(MutableAggregation aggregation, IMeasure measure) + { + return aggregation.Match( + (msum) => + { + return measure.Match( + (mdouble) => + { + return SumDataDouble.Create(msum.Sum); + }, + (mlong) => + { + return SumDataLong.Create((long)Math.Round(msum.Sum)); + }, + (invalid) => + { + throw new ArgumentException(); + }); + }, + CreateCountData, + CreateMeanData, + CreateDistributionData, + (mlval) => + { + return measure.Match( + (mdouble) => + { + return LastValueDataDouble.Create(mlval.LastValue); + }, + (mlong) => + { + if (double.IsNaN(mlval.LastValue)) + { + return LastValueDataLong.Create(0); + } + + return LastValueDataLong.Create((long)Math.Round(mlval.LastValue)); + }, + (invalid) => + { + throw new ArgumentException(); + }); + }); + } + + // Covert a mapping from TagValues to MutableAggregation, to a mapping from TagValues to + // AggregationData. + internal static IDictionary CreateAggregationMap(IDictionary tagValueAggregationMap, IMeasure measure) + { + IDictionary map = new Dictionary(); + foreach (var entry in tagValueAggregationMap) + { + map.Add(entry.Key, CreateAggregationData(entry.Value, measure)); + } + + return map; + } + + internal static MutableViewData Create(IView view, DateTimeOffset start) + { + return new CumulativeMutableViewData(view, start); + } + + /** Record double stats with the given tags. */ + internal abstract void Record(ITagContext context, double value, DateTimeOffset timestamp); + + /** Record long stats with the given tags. */ + internal void Record(ITagContext tags, long value, DateTimeOffset timestamp) + { + // TODO(songya): shall we check for precision loss here? + this.Record(tags, (double)value, timestamp); + } + + /** Convert this {@link MutableViewData} to {@link ViewData}. */ + internal abstract IViewData ToViewData(DateTimeOffset now, StatsCollectionState state); + + // Clear recorded stats. + internal abstract void ClearStats(); + + // Resume stats collection, and reset Start Timestamp (for CumulativeMutableViewData), or refresh + // bucket list (for InternalMutableViewData). + internal abstract void ResumeStatsCollection(DateTimeOffset now); + } +} diff --git a/src/OpenCensus/Stats/NoopMeasureMap.cs b/src/OpenCensus/Stats/NoopMeasureMap.cs new file mode 100644 index 000000000..d6418889f --- /dev/null +++ b/src/OpenCensus/Stats/NoopMeasureMap.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + + internal sealed class NoopMeasureMap : MeasureMapBase + { + internal static readonly NoopMeasureMap Instance = new NoopMeasureMap(); + + public override IMeasureMap Put(IMeasureDouble measure, double value) + { + return this; + } + + public override IMeasureMap Put(IMeasureLong measure, long value) + { + return this; + } + + public override void Record() + { + } + + public override void Record(ITagContext tags) + { + if (tags == null) + { + throw new ArgumentNullException(nameof(tags)); + } + } + } +} diff --git a/src/OpenCensus/Stats/NoopStats.cs b/src/OpenCensus/Stats/NoopStats.cs new file mode 100644 index 000000000..ddcd1bc54 --- /dev/null +++ b/src/OpenCensus/Stats/NoopStats.cs @@ -0,0 +1,51 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + internal sealed class NoopStats + { + private NoopStats() + { + } + + internal static IStatsRecorder NoopStatsRecorder + { + get + { + return OpenCensus.Stats.NoopStatsRecorder.Instance; + } + } + + internal static IMeasureMap NoopMeasureMap + { + get + { + return OpenCensus.Stats.NoopMeasureMap.Instance; + } + } + + internal static IStatsComponent NewNoopStatsComponent() + { + return new NoopStatsComponent(); + } + + internal static IViewManager NewNoopViewManager() + { + return new NoopViewManager(); + } + } +} diff --git a/src/OpenCensus/Stats/NoopStatsComponent.cs b/src/OpenCensus/Stats/NoopStatsComponent.cs new file mode 100644 index 000000000..482c06b63 --- /dev/null +++ b/src/OpenCensus/Stats/NoopStatsComponent.cs @@ -0,0 +1,53 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + internal sealed class NoopStatsComponent : StatsComponentBase + { + private readonly IViewManager viewManager = NoopStats.NewNoopViewManager(); + + // private volatile bool isRead; + public override IViewManager ViewManager + { + get + { + return this.viewManager; + } + } + + public override IStatsRecorder StatsRecorder + { + get + { + return NoopStats.NoopStatsRecorder; + } + } + + public override StatsCollectionState State + { + get + { + // isRead = true; + return StatsCollectionState.DISABLED; + } + + set + { + } + } + } +} diff --git a/src/OpenCensus/Stats/NoopStatsRecorder.cs b/src/OpenCensus/Stats/NoopStatsRecorder.cs new file mode 100644 index 000000000..d41d01d3a --- /dev/null +++ b/src/OpenCensus/Stats/NoopStatsRecorder.cs @@ -0,0 +1,28 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + internal sealed class NoopStatsRecorder : StatsRecorderBase + { + internal static readonly IStatsRecorder Instance = new NoopStatsRecorder(); + + public override IMeasureMap NewMeasureMap() + { + return NoopStats.NoopMeasureMap; + } + } +} diff --git a/src/OpenCensus/Stats/NoopViewManager.cs b/src/OpenCensus/Stats/NoopViewManager.cs new file mode 100644 index 000000000..39f511c39 --- /dev/null +++ b/src/OpenCensus/Stats/NoopViewManager.cs @@ -0,0 +1,116 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using OpenCensus.Common; + using OpenCensus.Tags; + + internal sealed class NoopViewManager : ViewManagerBase + { + private static readonly Timestamp ZeroTimestamp = Timestamp.Create(0, 0); + + private readonly IDictionary registeredViews = new Dictionary(); + + // Cached set of exported views. It must be set to null whenever a view is registered or + // unregistered. + private volatile ISet exportedViews; + + public override ISet AllExportedViews + { + get + { + ISet views = this.exportedViews; + if (views == null) + { + lock (this.registeredViews) + { + this.exportedViews = views = FilterExportedViews(this.registeredViews.Values); + return ImmutableHashSet.CreateRange(this.exportedViews); + } + } + + return views; + } + } + + public override void RegisterView(IView newView) + { + if (newView == null) + { + throw new ArgumentNullException(nameof(newView)); + } + + lock (this.registeredViews) + { + this.exportedViews = null; + this.registeredViews.TryGetValue(newView.Name, out IView existing); + if (!(existing == null || newView.Equals(existing))) + { + throw new ArgumentException("A different view with the same name already exists."); + } + + if (existing == null) + { + this.registeredViews.Add(newView.Name, newView); + } + } + } + + public override IViewData GetView(IViewName name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + lock (this.registeredViews) + { + this.registeredViews.TryGetValue(name, out IView view); + if (view == null) + { + return null; + } + else + { + return ViewData.Create( + view, + new Dictionary(), + DateTimeOffset.MinValue, + DateTimeOffset.MinValue); + } + } + } + + // Returns the subset of the given views that should be exported + private static ISet FilterExportedViews(ICollection allViews) + { + ISet views = new HashSet(); + foreach (IView view in allViews) + { + // if (view.getWindow() instanceof View.AggregationWindow.Interval) { + // continue; + // } + views.Add(view); + } + + return views; + } + } +} diff --git a/src/OpenCensus/Stats/Stats.cs b/src/OpenCensus/Stats/Stats.cs new file mode 100644 index 000000000..577bfea26 --- /dev/null +++ b/src/OpenCensus/Stats/Stats.cs @@ -0,0 +1,66 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + public class Stats + { + private static Stats stats = new Stats(); + + private IStatsComponent statsComponent = new StatsComponent(); + + internal Stats() + : this(true) + { + } + + internal Stats(bool enabled) + { + if (enabled) + { + this.statsComponent = new StatsComponent(); + } + else + { + this.statsComponent = NoopStats.NewNoopStatsComponent(); + } + } + + public static IStatsRecorder StatsRecorder + { + get + { + return stats.statsComponent.StatsRecorder; + } + } + + public static IViewManager ViewManager + { + get + { + return stats.statsComponent.ViewManager; + } + } + + public static StatsCollectionState State + { + get + { + return stats.statsComponent.State; + } + } + } +} diff --git a/src/OpenCensus/Stats/StatsComponent.cs b/src/OpenCensus/Stats/StatsComponent.cs new file mode 100644 index 000000000..43cc13631 --- /dev/null +++ b/src/OpenCensus/Stats/StatsComponent.cs @@ -0,0 +1,80 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using OpenCensus.Internal; + + public class StatsComponent : StatsComponentBase + { + // The StatsCollectionState shared between the StatsComponent, StatsRecorder and ViewManager. + private readonly CurrentStatsState state = new CurrentStatsState(); + + private readonly IViewManager viewManager; + private readonly IStatsRecorder statsRecorder; + + public StatsComponent() + : this(new SimpleEventQueue()) + { + } + + public StatsComponent(IEventQueue queue) + { + StatsManager statsManager = new StatsManager(queue, this.state); + this.viewManager = new ViewManager(statsManager); + this.statsRecorder = new StatsRecorder(statsManager); + } + + public override IViewManager ViewManager + { + get { return this.viewManager; } + } + + public override IStatsRecorder StatsRecorder + { + get { return this.statsRecorder; } + } + + public override StatsCollectionState State + { + get + { + return this.state.Value; + } + + set + { + if (!(this.viewManager is ViewManager manager)) + { + return; + } + + var result = this.state.Set(value); + if (result) + { + if (value == StatsCollectionState.DISABLED) + { + manager.ClearStats(); + } + else + { + manager.ResumeStatsCollection(); + } + } + } + } + } +} diff --git a/src/OpenCensus/Stats/StatsComponentBase.cs b/src/OpenCensus/Stats/StatsComponentBase.cs new file mode 100644 index 000000000..75c328b28 --- /dev/null +++ b/src/OpenCensus/Stats/StatsComponentBase.cs @@ -0,0 +1,27 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + public abstract class StatsComponentBase : IStatsComponent + { + public abstract IViewManager ViewManager { get; } + + public abstract IStatsRecorder StatsRecorder { get; } + + public abstract StatsCollectionState State { get; set; } + } +} diff --git a/src/OpenCensus/Stats/StatsExtensions.cs b/src/OpenCensus/Stats/StatsExtensions.cs new file mode 100644 index 000000000..6bb156437 --- /dev/null +++ b/src/OpenCensus/Stats/StatsExtensions.cs @@ -0,0 +1,235 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Tags; + + public static class StatsExtensions + { + public static bool ContainsKeys(this IView view, IEnumerable keys) + { + var columns = view.Columns; + foreach (var key in keys) + { + if (!columns.Contains(key)) + { + return false; + } + } + + return true; + } + + public static IAggregationData SumWithTags(this IViewData viewData, IEnumerable values = null) + { + return viewData.AggregationMap.WithTags(values).Sum(viewData.View); + } + + public static IDictionary WithTags(this IDictionary aggMap, IEnumerable values) + { + Dictionary results = new Dictionary(); + + foreach (var kvp in aggMap) + { + if (TagValuesMatch(kvp.Key.Values, values)) + { + results.Add(kvp.Key, kvp.Value); + } + } + + return results; + } + + public static IAggregationData Sum(this IDictionary aggMap, IView view) + { + var sum = MutableViewData.CreateMutableAggregation(view.Aggregation); + foreach (IAggregationData agData in aggMap.Values) + { + Sum(sum, agData); + } + + return MutableViewData.CreateAggregationData(sum, view.Measure); + } + + private static bool TagValuesMatch(IEnumerable aggValues, IEnumerable values) + { + if (values == null) + { + return true; + } + + if (aggValues.Count() != values.Count()) + { + return false; + } + + var first = aggValues.GetEnumerator(); + var second = values.GetEnumerator(); + + while (first.MoveNext()) + { + second.MoveNext(); + + // Null matches any aggValue + if (second.Current == null) + { + continue; + } + + if (first.Current != second.Current) + { + return false; + } + } + + return true; + } + + private static void Sum(MutableAggregation combined, IAggregationData data) + { + data.Match( + (arg) => + { + if (combined is MutableSum sum) + { + sum.Add(arg.Sum); + } + + return null; + }, + (arg) => + { + if (combined is MutableSum sum) + { + sum.Add(arg.Sum); + } + + return null; + }, + (arg) => + { + if (combined is MutableCount count) + { + count.Add(arg.Count); + } + + return null; + }, + (arg) => + { + if (combined is MutableMean mean) + { + mean.Count = mean.Count + arg.Count; + mean.Sum = mean.Sum + (arg.Count * arg.Mean); + if (arg.Min < mean.Min) + { + mean.Min = arg.Min; + } + + if (arg.Max > mean.Max) + { + mean.Max = arg.Max; + } + } + + return null; + }, + (arg) => + { + if (combined is MutableDistribution dist) + { + // Algorithm for calculating the combination of sum of squared deviations: + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm. + if (dist.Count + arg.Count > 0) + { + double delta = arg.Mean - dist.Mean; + dist.SumOfSquaredDeviations = + dist.SumOfSquaredDeviations + + arg.SumOfSquaredDeviations + + (Math.Pow(delta, 2) + * dist.Count + * arg.Count + / (dist.Count + arg.Count)); + } + + dist.Count += arg.Count; + dist.Sum += arg.Mean * arg.Count; + dist.Mean = dist.Sum / dist.Count; + + if (arg.Min < dist.Min) + { + dist.Min = arg.Min; + } + + if (arg.Max > dist.Max) + { + dist.Max = arg.Max; + } + + IReadOnlyList bucketCounts = arg.BucketCounts; + for (int i = 0; i < bucketCounts.Count; i++) + { + dist.BucketCounts[i] += bucketCounts[i]; + } + } + + return null; + }, + (arg) => + { + if (combined is MutableLastValue lastValue) + { + lastValue.Initialized = true; + if (double.IsNaN(lastValue.LastValue)) + { + lastValue.LastValue = arg.LastValue; + } + else + { + lastValue.LastValue += arg.LastValue; + } + } + + return null; + }, + (arg) => + { + if (combined is MutableLastValue lastValue) + { + lastValue.Initialized = true; + if (double.IsNaN(lastValue.LastValue)) + { + lastValue.LastValue = arg.LastValue; + } + else + { + lastValue.LastValue += arg.LastValue; + } + } + + return null; + }, + (arg) => + { + throw new ArgumentException(); + }); + } + } +} diff --git a/src/OpenCensus/Stats/StatsManager.cs b/src/OpenCensus/Stats/StatsManager.cs new file mode 100644 index 000000000..f0358b988 --- /dev/null +++ b/src/OpenCensus/Stats/StatsManager.cs @@ -0,0 +1,95 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using OpenCensus.Internal; + using OpenCensus.Tags; + + internal sealed class StatsManager + { + private readonly IEventQueue queue; + + private readonly CurrentStatsState state; + private readonly MeasureToViewMap measureToViewMap = new MeasureToViewMap(); + + internal StatsManager(IEventQueue queue, CurrentStatsState state) + { + this.queue = queue ?? throw new ArgumentNullException(nameof(queue)); + this.state = state ?? throw new ArgumentNullException(nameof(state)); + } + + internal ISet ExportedViews + { + get + { + return this.measureToViewMap.ExportedViews; + } + } + + internal void RegisterView(IView view) + { + this.measureToViewMap.RegisterView(view); + } + + internal IViewData GetView(IViewName viewName) + { + return this.measureToViewMap.GetView(viewName, this.state.Internal); + } + + internal void Record(ITagContext tags, IEnumerable measurementValues) + { + // TODO(songya): consider exposing No-op MeasureMap and use it when stats state is DISABLED, so + // that we don't need to create actual MeasureMapImpl. + if (this.state.Internal == StatsCollectionState.ENABLED) + { + this.queue.Enqueue(new StatsEvent(this, tags, measurementValues)); + } + } + + internal void ClearStats() + { + this.measureToViewMap.ClearStats(); + } + + internal void ResumeStatsCollection() + { + this.measureToViewMap.ResumeStatsCollection(DateTimeOffset.Now); + } + + private class StatsEvent : IEventQueueEntry + { + private readonly ITagContext tags; + private readonly IEnumerable stats; + private readonly StatsManager statsManager; + + public StatsEvent(StatsManager statsManager, ITagContext tags, IEnumerable stats) + { + this.statsManager = statsManager; + this.tags = tags; + this.stats = stats; + } + + public void Process() + { + // Add Timestamp to value after it went through the DisruptorQueue. + this.statsManager.measureToViewMap.Record(this.tags, this.stats, DateTimeOffset.Now); + } + } +} +} diff --git a/src/OpenCensus/Stats/StatsRecorder.cs b/src/OpenCensus/Stats/StatsRecorder.cs new file mode 100644 index 000000000..5ed30748a --- /dev/null +++ b/src/OpenCensus/Stats/StatsRecorder.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + + public sealed class StatsRecorder : StatsRecorderBase + { + private readonly StatsManager statsManager; + + internal StatsRecorder(StatsManager statsManager) + { + this.statsManager = statsManager ?? throw new ArgumentNullException(nameof(statsManager)); + } + + public override IMeasureMap NewMeasureMap() + { + return MeasureMap.Create(this.statsManager); + } + } +} diff --git a/src/OpenCensus/Stats/StatsRecorderBase.cs b/src/OpenCensus/Stats/StatsRecorderBase.cs new file mode 100644 index 000000000..322370b43 --- /dev/null +++ b/src/OpenCensus/Stats/StatsRecorderBase.cs @@ -0,0 +1,23 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + public abstract class StatsRecorderBase : IStatsRecorder + { + public abstract IMeasureMap NewMeasureMap(); + } +} diff --git a/src/OpenCensus/Stats/View.cs b/src/OpenCensus/Stats/View.cs new file mode 100644 index 000000000..dd779c0b5 --- /dev/null +++ b/src/OpenCensus/Stats/View.cs @@ -0,0 +1,111 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Tags; + + public sealed class View : IView + { + internal View(IViewName name, string description, IMeasure measure, IAggregation aggregation, IReadOnlyList columns) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + this.Measure = measure ?? throw new ArgumentNullException(nameof(measure)); + this.Aggregation = aggregation ?? throw new ArgumentNullException(nameof(aggregation)); + this.Columns = columns ?? throw new ArgumentNullException(nameof(columns)); + } + + public IViewName Name { get; } + + public string Description { get; } + + public IMeasure Measure { get; } + + public IAggregation Aggregation { get; } + + public IReadOnlyList Columns { get; } + + public static IView Create(IViewName name, string description, IMeasure measure, IAggregation aggregation, IReadOnlyList columns) + { + var set = new HashSet(columns); + if (set.Count != columns.Count) + { + throw new ArgumentException("Columns have duplicate."); + } + + return new View( + name, + description, + measure, + aggregation, + new List(columns).AsReadOnly()); + } + + /// + public override string ToString() + { + return "View{" + + "name=" + this.Name + ", " + + "description=" + this.Description + ", " + + "measure=" + this.Measure + ", " + + "aggregation=" + this.Aggregation + ", " + + "columns=" + this.Columns + ", " + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is View that) + { + return this.Name.Equals(that.Name) + && this.Description.Equals(that.Description) + && this.Measure.Equals(that.Measure) + && this.Aggregation.Equals(that.Aggregation) + && this.Columns.SequenceEqual(that.Columns); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Name.GetHashCode(); + h *= 1000003; + h ^= this.Description.GetHashCode(); + h *= 1000003; + h ^= this.Measure.GetHashCode(); + h *= 1000003; + h ^= this.Aggregation.GetHashCode(); + h *= 1000003; + h ^= this.Columns.GetHashCode(); + h *= 1000003; + return h; + } + } +} diff --git a/src/OpenCensus/Stats/ViewData.cs b/src/OpenCensus/Stats/ViewData.cs new file mode 100644 index 000000000..378b51b57 --- /dev/null +++ b/src/OpenCensus/Stats/ViewData.cs @@ -0,0 +1,205 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Tags; + using OpenCensus.Utils; + + public sealed class ViewData : IViewData + { + internal ViewData(IView view, IDictionary aggregationMap, DateTimeOffset start, DateTimeOffset end) + { + this.View = view ?? throw new ArgumentNullException(nameof(view)); + this.AggregationMap = aggregationMap ?? throw new ArgumentNullException(nameof(aggregationMap)); + this.Start = start; + this.End = end; + } + + public IView View { get; } + + public IDictionary AggregationMap { get; } + + public DateTimeOffset Start { get; } + + public DateTimeOffset End { get; } + + public static IViewData Create(IView view, IDictionary map, DateTimeOffset start, DateTimeOffset end) + { + IDictionary deepCopy = new Dictionary(); + foreach (var entry in map) + { + CheckAggregation(view.Aggregation, entry.Value, view.Measure); + deepCopy.Add(entry.Key, entry.Value); + } + + return new ViewData( + view, + new ReadOnlyDictionary(deepCopy), + start, + end); + } + + /// + public override string ToString() + { + return "ViewData{" + + "view=" + this.View + ", " + + "aggregationMap=" + Collections.ToString(this.AggregationMap) + ", " + + "start=" + this.Start + ", " + + "end=" + this.End + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is ViewData that) + { + return this.View.Equals(that.View) + && this.AggregationMap.SequenceEqual(that.AggregationMap) + && this.Start.Equals(that.Start) + && this.End.Equals(that.End); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.View.GetHashCode(); + h *= 1000003; + h ^= this.AggregationMap.GetHashCode(); + h *= 1000003; + h ^= this.Start.GetHashCode(); + h *= 1000003; + h ^= this.End.GetHashCode(); + return h; + } + + private static void CheckAggregation(IAggregation aggregation, IAggregationData aggregationData, IMeasure measure) + { + aggregation.Match( + (arg) => + { + measure.Match( + (arg1) => + { + if (!(aggregationData is ISumDataDouble)) + { + throw new ArgumentException(CreateErrorMessageForAggregation(aggregation, aggregationData)); + } + + return null; + }, + (arg1) => + { + if (!(aggregationData is ISumDataLong)) + { + throw new ArgumentException(CreateErrorMessageForAggregation(aggregation, aggregationData)); + } + + return null; + }, + (arg1) => + { + throw new ArgumentException(); + }); + return null; + }, + (arg) => + { + if (!(aggregationData is ICountData)) + { + throw new ArgumentException(CreateErrorMessageForAggregation(aggregation, aggregationData)); + } + + return null; + }, + (arg) => + { + if (!(aggregationData is IMeanData)) + { + throw new ArgumentException(CreateErrorMessageForAggregation(aggregation, aggregationData)); + } + + return null; + }, + (arg) => + { + if (!(aggregationData is IDistributionData)) + { + throw new ArgumentException(CreateErrorMessageForAggregation(aggregation, aggregationData)); + } + + return null; + }, + (arg) => + { + measure.Match( + (arg1) => + { + if (!(aggregationData is ILastValueDataDouble)) + { + throw new ArgumentException(CreateErrorMessageForAggregation(aggregation, aggregationData)); + } + + return null; + }, + (arg1) => + { + if (!(aggregationData is ILastValueDataLong)) + { + throw new ArgumentException(CreateErrorMessageForAggregation(aggregation, aggregationData)); + } + + return null; + }, + (arg1) => + { + throw new ArgumentException(); + }); + return null; + }, + (arg) => + { + throw new ArgumentException(); + }); + } + + private static string CreateErrorMessageForAggregation(IAggregation aggregation, IAggregationData aggregationData) + { + return "Aggregation and AggregationData types mismatch. " + + "Aggregation: " + + aggregation + + " AggregationData: " + + aggregationData; + } + } +} diff --git a/src/OpenCensus/Stats/ViewManager.cs b/src/OpenCensus/Stats/ViewManager.cs new file mode 100644 index 000000000..f50a69e91 --- /dev/null +++ b/src/OpenCensus/Stats/ViewManager.cs @@ -0,0 +1,58 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System.Collections.Generic; + + public sealed class ViewManager : ViewManagerBase + { + private readonly StatsManager statsManager; + + internal ViewManager(StatsManager statsManager) + { + this.statsManager = statsManager; + } + + public override ISet AllExportedViews + { + get + { + return this.statsManager.ExportedViews; + } + } + + public override void RegisterView(IView view) + { + this.statsManager.RegisterView(view); + } + + public override IViewData GetView(IViewName viewName) + { + return this.statsManager.GetView(viewName); + } + + internal void ClearStats() + { + this.statsManager.ClearStats(); + } + + internal void ResumeStatsCollection() + { + this.statsManager.ResumeStatsCollection(); + } + } +} diff --git a/src/OpenCensus/Stats/ViewManagerBase.cs b/src/OpenCensus/Stats/ViewManagerBase.cs new file mode 100644 index 000000000..192c1fb8c --- /dev/null +++ b/src/OpenCensus/Stats/ViewManagerBase.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System.Collections.Generic; + + public abstract class ViewManagerBase : IViewManager + { + public abstract ISet AllExportedViews { get; } + + public abstract IViewData GetView(IViewName view); + + public abstract void RegisterView(IView view); + } +} diff --git a/src/OpenCensus/Stats/ViewName.cs b/src/OpenCensus/Stats/ViewName.cs new file mode 100644 index 000000000..aa7e0c96c --- /dev/null +++ b/src/OpenCensus/Stats/ViewName.cs @@ -0,0 +1,84 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats +{ + using System; + using OpenCensus.Utils; + + public sealed class ViewName : IViewName + { + internal const int NameMaxLength = 255; + + internal ViewName(string asString) + { + this.AsString = asString ?? throw new ArgumentNullException(nameof(asString)); + } + + public string AsString { get; } + + public static IViewName Create(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!(StringUtil.IsPrintableString(name) && name.Length <= NameMaxLength)) + { + throw new ArgumentOutOfRangeException( + "Name should be a ASCII string with a length no greater than " + + NameMaxLength + + " characters."); + } + + return new ViewName(name); + } + + /// + public override string ToString() + { + return "Name{" + + "asString=" + this.AsString + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is ViewName that) + { + return this.AsString.Equals(that.AsString); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.AsString.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Tags/CurrentTagContextUtils.cs b/src/OpenCensus/Tags/CurrentTagContextUtils.cs new file mode 100644 index 000000000..721a25df6 --- /dev/null +++ b/src/OpenCensus/Tags/CurrentTagContextUtils.cs @@ -0,0 +1,56 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Common; + using OpenCensus.Tags.Unsafe; + + internal static class CurrentTagContextUtils + { + internal static ITagContext CurrentTagContext + { + get { return AsyncLocalContext.CurrentTagContext; } + } + + internal static IScope WithTagContext(ITagContext tags) + { + return new WithTagContextScope(tags); + } + + private sealed class WithTagContextScope : IScope + { + private readonly ITagContext origContext; + + public WithTagContextScope(ITagContext tags) + { + this.origContext = AsyncLocalContext.CurrentTagContext; + AsyncLocalContext.CurrentTagContext = tags; + } + + public void Dispose() + { + var current = AsyncLocalContext.CurrentTagContext; + AsyncLocalContext.CurrentTagContext = this.origContext; + + if (current != this.origContext) + { + // Log + } + } + } + } +} diff --git a/src/OpenCensus/Tags/CurrentTaggingState.cs b/src/OpenCensus/Tags/CurrentTaggingState.cs new file mode 100644 index 000000000..e6678a986 --- /dev/null +++ b/src/OpenCensus/Tags/CurrentTaggingState.cs @@ -0,0 +1,64 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + + public sealed class CurrentTaggingState + { + private readonly object lck = new object(); + private TaggingState currentState = TaggingState.ENABLED; + private bool isRead; + + public TaggingState Value + { + get + { + lock (this.lck) + { + this.isRead = true; + return this.Internal; + } + } + } + + public TaggingState Internal + { + get + { + lock (this.lck) + { + return this.currentState; + } + } + } + + // Sets current state to the given state. + internal void Set(TaggingState state) + { + lock (this.lck) + { + if (this.isRead) + { + throw new InvalidOperationException("State was already read, cannot set state."); + } + + this.currentState = state; + } + } + } +} diff --git a/src/OpenCensus/Tags/NoopTagContext.cs b/src/OpenCensus/Tags/NoopTagContext.cs new file mode 100644 index 000000000..732516d39 --- /dev/null +++ b/src/OpenCensus/Tags/NoopTagContext.cs @@ -0,0 +1,30 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System.Collections.Generic; + + public sealed class NoopTagContext : TagContextBase + { + internal static readonly ITagContext Instance = new NoopTagContext(); + + public override IEnumerator GetEnumerator() + { + return new List().GetEnumerator(); + } + } +} diff --git a/src/OpenCensus/Tags/NoopTagContextBinarySerializer.cs b/src/OpenCensus/Tags/NoopTagContextBinarySerializer.cs new file mode 100644 index 000000000..ac0b7f88b --- /dev/null +++ b/src/OpenCensus/Tags/NoopTagContextBinarySerializer.cs @@ -0,0 +1,47 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + using OpenCensus.Tags.Propagation; + + public class NoopTagContextBinarySerializer : TagContextBinarySerializerBase + { + internal static readonly ITagContextBinarySerializer Instance = new NoopTagContextBinarySerializer(); + private static readonly byte[] EmptyByteArray = { }; + + public override byte[] ToByteArray(ITagContext tags) + { + if (tags == null) + { + throw new ArgumentNullException(nameof(tags)); + } + + return EmptyByteArray; + } + + public override ITagContext FromByteArray(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + return NoopTags.NoopTagContext; + } + } +} diff --git a/src/OpenCensus/Tags/NoopTagContextBuilder.cs b/src/OpenCensus/Tags/NoopTagContextBuilder.cs new file mode 100644 index 000000000..3c00c1052 --- /dev/null +++ b/src/OpenCensus/Tags/NoopTagContextBuilder.cs @@ -0,0 +1,66 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + using OpenCensus.Common; + using OpenCensus.Internal; + + internal sealed class NoopTagContextBuilder : TagContextBuilderBase + { + internal static readonly ITagContextBuilder Instance = new NoopTagContextBuilder(); + + private NoopTagContextBuilder() + { + } + + public override ITagContextBuilder Put(ITagKey key, ITagValue value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + return this; + } + + public override ITagContextBuilder Remove(ITagKey key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return this; + } + + public override ITagContext Build() + { + return NoopTagContext.Instance; + } + + public override IScope BuildScoped() + { + return NoopScope.Instance; + } + } +} diff --git a/src/OpenCensus/Tags/NoopTagPropagationComponent.cs b/src/OpenCensus/Tags/NoopTagPropagationComponent.cs new file mode 100644 index 000000000..2b39621da --- /dev/null +++ b/src/OpenCensus/Tags/NoopTagPropagationComponent.cs @@ -0,0 +1,33 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Tags.Propagation; + + public class NoopTagPropagationComponent : TagPropagationComponentBase + { + internal static readonly ITagPropagationComponent Instance = new NoopTagPropagationComponent(); + + public override ITagContextBinarySerializer BinarySerializer + { + get + { + return NoopTags.NoopTagContextBinarySerializer; + } + } + } +} diff --git a/src/OpenCensus/Tags/NoopTagger.cs b/src/OpenCensus/Tags/NoopTagger.cs new file mode 100644 index 000000000..089a25840 --- /dev/null +++ b/src/OpenCensus/Tags/NoopTagger.cs @@ -0,0 +1,79 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + using OpenCensus.Common; + using OpenCensus.Internal; + + internal sealed class NoopTagger : TaggerBase + { + internal static readonly ITagger Instance = new NoopTagger(); + + public override ITagContext Empty + { + get + { + return NoopTags.NoopTagContext; + } + } + + public override ITagContext CurrentTagContext + { + get + { + return NoopTags.NoopTagContext; + } + } + + public override ITagContextBuilder EmptyBuilder + { + get + { + return NoopTags.NoopTagContextBuilder; + } + } + + public override ITagContextBuilder CurrentBuilder + { + get + { + return NoopTags.NoopTagContextBuilder; + } + } + + public override ITagContextBuilder ToBuilder(ITagContext tags) + { + if (tags == null) + { + throw new ArgumentNullException(nameof(tags)); + } + + return NoopTags.NoopTagContextBuilder; + } + + public override IScope WithTagContext(ITagContext tags) + { + if (tags == null) + { + throw new ArgumentNullException(nameof(tags)); + } + + return NoopScope.Instance; + } + } +} diff --git a/src/OpenCensus/Tags/NoopTags.cs b/src/OpenCensus/Tags/NoopTags.cs new file mode 100644 index 000000000..d8bf0480c --- /dev/null +++ b/src/OpenCensus/Tags/NoopTags.cs @@ -0,0 +1,68 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Tags.Propagation; + + internal sealed class NoopTags + { + internal static ITagger NoopTagger + { + get + { + return OpenCensus.Tags.NoopTagger.Instance; + } + } + + internal static ITagContextBuilder NoopTagContextBuilder + { + get + { + return OpenCensus.Tags.NoopTagContextBuilder.Instance; + } + } + + internal static ITagContext NoopTagContext + { + get + { + return OpenCensus.Tags.NoopTagContext.Instance; + } + } + + internal static ITagPropagationComponent NoopTagPropagationComponent + { + get + { + return OpenCensus.Tags.NoopTagPropagationComponent.Instance; + } + } + + internal static ITagContextBinarySerializer NoopTagContextBinarySerializer + { + get + { + return OpenCensus.Tags.NoopTagContextBinarySerializer.Instance; + } + } + + internal static ITagsComponent NewNoopTagsComponent() + { + return new NoopTagsComponent(); + } + } +} diff --git a/src/OpenCensus/Tags/NoopTagsComponent.cs b/src/OpenCensus/Tags/NoopTagsComponent.cs new file mode 100644 index 000000000..fecaeb105 --- /dev/null +++ b/src/OpenCensus/Tags/NoopTagsComponent.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Tags.Propagation; + + public sealed class NoopTagsComponent : TagsComponentBase + { + // private volatile bool isRead; + public override ITagger Tagger + { + get + { + return NoopTags.NoopTagger; + } + } + + public override ITagPropagationComponent TagPropagationComponent + { + get + { + return NoopTags.NoopTagPropagationComponent; + } + } + + public override TaggingState State + { + get + { + // isRead = true; + return TaggingState.DISABLED; + } + } + } +} diff --git a/src/OpenCensus/Tags/Propagation/SerializationUtils.cs b/src/OpenCensus/Tags/Propagation/SerializationUtils.cs new file mode 100644 index 000000000..8ac9e7486 --- /dev/null +++ b/src/OpenCensus/Tags/Propagation/SerializationUtils.cs @@ -0,0 +1,190 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using OpenCensus.Internal; + + internal static class SerializationUtils + { + internal const int VersionId = 0; + internal const int TagFieldId = 0; + + // This size limit only applies to the bytes representing tag keys and values. + internal const int TagContextSerializedSizeLimit = 8192; + + // Serializes a TagContext to the on-the-wire format. + // Encoded tags are of the form: + internal static byte[] SerializeBinary(ITagContext tags) + { + // Use a ByteArrayDataOutput to avoid needing to handle IOExceptions. + // ByteArrayDataOutput byteArrayDataOutput = ByteStreams.newDataOutput(); + MemoryStream byteArrayDataOutput = new MemoryStream(); + + byteArrayDataOutput.WriteByte(VersionId); + int totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars. + foreach (var tag in tags) + { + totalChars += tag.Key.Name.Length; + totalChars += tag.Value.AsString.Length; + EncodeTag(tag, byteArrayDataOutput); + } + + // for (Iterator i = InternalUtils.getTags(tags); i.hasNext();) { + // Tag tag = i.next(); + // totalChars += tag.getKey().getName().length(); + // totalChars += tag.getValue().asString().length(); + // encodeTag(tag, byteArrayDataOutput); + // } + if (totalChars > TagContextSerializedSizeLimit) + { + throw new TagContextSerializationException( + "Size of TagContext exceeds the maximum serialized size " + + TagContextSerializedSizeLimit); + } + + return byteArrayDataOutput.ToArray(); + } + + // Deserializes input to TagContext based on the binary format standard. + // The encoded tags are of the form: + internal static ITagContext DeserializeBinary(byte[] bytes) + { + try + { + if (bytes.Length == 0) + { + // Does not allow empty byte array. + throw new TagContextDeserializationException("Input byte[] can not be empty."); + } + + MemoryStream buffer = new MemoryStream(bytes); + int versionId = buffer.ReadByte(); + if (versionId > VersionId || versionId < 0) + { + throw new TagContextDeserializationException( + "Wrong Version ID: " + versionId + ". Currently supports version up to: " + VersionId); + } + + return new TagContext(ParseTags(buffer)); + } + catch (Exception exn) + { + throw new TagContextDeserializationException(exn.ToString()); // byte array format error. + } + } + + internal static IDictionary ParseTags(MemoryStream buffer) + { + IDictionary tags = new Dictionary(); + long limit = buffer.Length; + int totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars. + while (buffer.Position < limit) + { + int type = buffer.ReadByte(); + if (type == TagFieldId) + { + ITagKey key = CreateTagKey(DecodeString(buffer)); + ITagValue val = CreateTagValue(key, DecodeString(buffer)); + totalChars += key.Name.Length; + totalChars += val.AsString.Length; + tags[key] = val; + } +else + { + // Stop parsing at the first unknown field ID, since there is no way to know its length. + // TODO(sebright): Consider storing the rest of the byte array in the TagContext. + break; + } + } + + if (totalChars > TagContextSerializedSizeLimit) + { + throw new TagContextDeserializationException( + "Size of TagContext exceeds the maximum serialized size " + + TagContextSerializedSizeLimit); + } + + return tags; + } + + // TODO(sebright): Consider exposing a TagKey name validation method to avoid needing to catch an + // IllegalArgumentException here. + private static ITagKey CreateTagKey(string name) + { + try + { + return TagKey.Create(name); + } + catch (Exception e) + { + throw new TagContextDeserializationException("Invalid tag key: " + name, e); + } + } + + // TODO(sebright): Consider exposing a TagValue validation method to avoid needing to catch + // an IllegalArgumentException here. + private static ITagValue CreateTagValue(ITagKey key, string value) + { + try + { + return TagValue.Create(value); + } + catch (Exception e) + { + throw new TagContextDeserializationException( + "Invalid tag value for key " + key + ": " + value, e); + } + } + + private static void EncodeTag(ITag tag, MemoryStream byteArrayDataOutput) + { + byteArrayDataOutput.WriteByte(TagFieldId); + EncodeString(tag.Key.Name, byteArrayDataOutput); + EncodeString(tag.Value.AsString, byteArrayDataOutput); + } + + private static void EncodeString(string input, MemoryStream byteArrayDataOutput) + { + PutVarInt(input.Length, byteArrayDataOutput); + var bytes = Encoding.UTF8.GetBytes(input); + byteArrayDataOutput.Write(bytes, 0, bytes.Length); + } + + private static void PutVarInt(int input, MemoryStream byteArrayDataOutput) + { + byte[] output = new byte[VarInt.VarIntSize(input)]; + VarInt.PutVarInt(input, output, 0); + byteArrayDataOutput.Write(output, 0, output.Length); + } + + private static string DecodeString(MemoryStream buffer) + { + int length = VarInt.GetVarInt(buffer); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) + { + builder.Append((char)buffer.ReadByte()); + } + + return builder.ToString(); + } + } +} diff --git a/src/OpenCensus/Tags/Propagation/TagContextBinarySerializer.cs b/src/OpenCensus/Tags/Propagation/TagContextBinarySerializer.cs new file mode 100644 index 000000000..da04068c4 --- /dev/null +++ b/src/OpenCensus/Tags/Propagation/TagContextBinarySerializer.cs @@ -0,0 +1,44 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + internal sealed class TagContextBinarySerializer : TagContextBinarySerializerBase + { + private static readonly byte[] EmptyByteArray = { }; + + private readonly CurrentTaggingState state; + + internal TagContextBinarySerializer(CurrentTaggingState state) + { + this.state = state; + } + + public override byte[] ToByteArray(ITagContext tags) + { + return this.state.Internal == TaggingState.DISABLED + ? EmptyByteArray + : SerializationUtils.SerializeBinary(tags); + } + + public override ITagContext FromByteArray(byte[] bytes) + { + return this.state.Internal == TaggingState.DISABLED + ? TagContext.Empty + : SerializationUtils.DeserializeBinary(bytes); + } + } +} diff --git a/src/OpenCensus/Tags/Propagation/TagContextBinarySerializerBase.cs b/src/OpenCensus/Tags/Propagation/TagContextBinarySerializerBase.cs new file mode 100644 index 000000000..0f5732472 --- /dev/null +++ b/src/OpenCensus/Tags/Propagation/TagContextBinarySerializerBase.cs @@ -0,0 +1,25 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + public abstract class TagContextBinarySerializerBase : ITagContextBinarySerializer + { + public abstract ITagContext FromByteArray(byte[] bytes); + + public abstract byte[] ToByteArray(ITagContext tags); + } +} diff --git a/src/OpenCensus/Tags/Propagation/TagContextDeserializationException.cs b/src/OpenCensus/Tags/Propagation/TagContextDeserializationException.cs new file mode 100644 index 000000000..889babb2f --- /dev/null +++ b/src/OpenCensus/Tags/Propagation/TagContextDeserializationException.cs @@ -0,0 +1,33 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + using System; + + public sealed class TagContextDeserializationException : Exception + { + public TagContextDeserializationException(string message) + : base(message) + { + } + + public TagContextDeserializationException(string message, Exception cause) + : base(message, cause) + { + } + } +} diff --git a/src/OpenCensus/Tags/Propagation/TagContextSerializationException.cs b/src/OpenCensus/Tags/Propagation/TagContextSerializationException.cs new file mode 100644 index 000000000..5ad80309e --- /dev/null +++ b/src/OpenCensus/Tags/Propagation/TagContextSerializationException.cs @@ -0,0 +1,33 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + using System; + + public sealed class TagContextSerializationException : Exception + { + public TagContextSerializationException(string message) + : base(message) + { + } + + public TagContextSerializationException(string message, Exception cause) + : base(message, cause) + { + } + } +} diff --git a/src/OpenCensus/Tags/Propagation/TagPropagationComponent.cs b/src/OpenCensus/Tags/Propagation/TagPropagationComponent.cs new file mode 100644 index 000000000..281bb91cc --- /dev/null +++ b/src/OpenCensus/Tags/Propagation/TagPropagationComponent.cs @@ -0,0 +1,33 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + public sealed class TagPropagationComponent : TagPropagationComponentBase + { + private readonly ITagContextBinarySerializer tagContextBinarySerializer; + + public TagPropagationComponent(CurrentTaggingState state) + { + this.tagContextBinarySerializer = new TagContextBinarySerializer(state); + } + + public override ITagContextBinarySerializer BinarySerializer + { + get { return this.tagContextBinarySerializer; } + } + } +} diff --git a/src/OpenCensus/Tags/Propagation/TagPropagationComponentBase.cs b/src/OpenCensus/Tags/Propagation/TagPropagationComponentBase.cs new file mode 100644 index 000000000..ca655257b --- /dev/null +++ b/src/OpenCensus/Tags/Propagation/TagPropagationComponentBase.cs @@ -0,0 +1,27 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation +{ + public abstract class TagPropagationComponentBase : ITagPropagationComponent + { + protected TagPropagationComponentBase() + { + } + + public abstract ITagContextBinarySerializer BinarySerializer { get; } + } +} diff --git a/src/OpenCensus/Tags/Tag.cs b/src/OpenCensus/Tags/Tag.cs new file mode 100644 index 000000000..a5062c0c7 --- /dev/null +++ b/src/OpenCensus/Tags/Tag.cs @@ -0,0 +1,75 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + + public sealed class Tag : ITag + { + internal Tag(ITagKey key, ITagValue value) + { + this.Key = key ?? throw new ArgumentNullException(nameof(key)); + this.Value = value ?? throw new ArgumentNullException(nameof(value)); + } + + public ITagKey Key { get; } + + public ITagValue Value { get; } + + public static ITag Create(ITagKey key, ITagValue value) + { + return new Tag(key, value); + } + + /// + public override string ToString() + { + return "Tag{" + + "key=" + this.Key + ", " + + "value=" + this.Value + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Tag that) + { + return this.Key.Equals(that.Key) + && this.Value.Equals(that.Value); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Key.GetHashCode(); + h *= 1000003; + h ^= this.Value.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Tags/TagContext.cs b/src/OpenCensus/Tags/TagContext.cs new file mode 100644 index 000000000..3d26bca8d --- /dev/null +++ b/src/OpenCensus/Tags/TagContext.cs @@ -0,0 +1,40 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + public sealed class TagContext : TagContextBase + { + public static readonly ITagContext Empty = new TagContext(new Dictionary()); + + public TagContext(IDictionary tags) + { + this.Tags = new ReadOnlyDictionary(new Dictionary(tags)); + } + + public IDictionary Tags { get; } + + public override IEnumerator GetEnumerator() + { + var result = this.Tags.Select((kvp) => Tag.Create(kvp.Key, kvp.Value)); + return result.ToList().GetEnumerator(); + } + } +} diff --git a/src/OpenCensus/Tags/TagContextBase.cs b/src/OpenCensus/Tags/TagContextBase.cs new file mode 100644 index 000000000..a1b61051f --- /dev/null +++ b/src/OpenCensus/Tags/TagContextBase.cs @@ -0,0 +1,88 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Utils; + + public abstract class TagContextBase : ITagContext + { + /// + public override string ToString() + { + return "TagContext"; + } + + /// + public override bool Equals(object other) + { + if (!(other is TagContextBase)) + { + return false; + } + + TagContextBase otherTags = (TagContextBase)other; + + var t1Enumerator = this.GetEnumerator(); + var t2Enumerator = otherTags.GetEnumerator(); + + List tags1 = null; + List tags2 = null; + + if (t1Enumerator == null) + { + tags1 = new List(); + } + else + { + tags1 = this.ToList(); + } + + if (t2Enumerator == null) + { + tags2 = new List(); + } + else + { + tags2 = otherTags.ToList(); + } + + return Collections.AreEquivalent(tags1, tags2); + } + + /// + public override int GetHashCode() + { + int hashCode = 0; + foreach (var t in this) + { + hashCode += t.GetHashCode(); + } + + return hashCode; + } + + public abstract IEnumerator GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/src/OpenCensus/Tags/TagContextBuilder.cs b/src/OpenCensus/Tags/TagContextBuilder.cs new file mode 100644 index 000000000..b79e7a297 --- /dev/null +++ b/src/OpenCensus/Tags/TagContextBuilder.cs @@ -0,0 +1,73 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + using System.Collections.Generic; + using OpenCensus.Common; + + internal sealed class TagContextBuilder : TagContextBuilderBase + { + internal TagContextBuilder(IDictionary tags) + { + this.Tags = new Dictionary(tags); + } + + internal TagContextBuilder() + { + this.Tags = new Dictionary(); + } + + internal IDictionary Tags { get; } + + public override ITagContextBuilder Put(ITagKey key, ITagValue value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + this.Tags[key] = value ?? throw new ArgumentNullException(nameof(value)); + return this; + } + + public override ITagContextBuilder Remove(ITagKey key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (this.Tags.ContainsKey(key)) + { + this.Tags.Remove(key); + } + + return this; + } + + public override ITagContext Build() + { + return new TagContext(this.Tags); + } + + public override IScope BuildScoped() + { + return CurrentTagContextUtils.WithTagContext(this.Build()); + } + } +} diff --git a/src/OpenCensus/Tags/TagContextBuilderBase.cs b/src/OpenCensus/Tags/TagContextBuilderBase.cs new file mode 100644 index 000000000..20bfe9318 --- /dev/null +++ b/src/OpenCensus/Tags/TagContextBuilderBase.cs @@ -0,0 +1,31 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Common; + + public abstract class TagContextBuilderBase : ITagContextBuilder + { + public abstract ITagContext Build(); + + public abstract IScope BuildScoped(); + + public abstract ITagContextBuilder Put(ITagKey key, ITagValue value); + + public abstract ITagContextBuilder Remove(ITagKey key); + } +} diff --git a/src/OpenCensus/Tags/TagKey.cs b/src/OpenCensus/Tags/TagKey.cs new file mode 100644 index 000000000..9e1820361 --- /dev/null +++ b/src/OpenCensus/Tags/TagKey.cs @@ -0,0 +1,81 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + using OpenCensus.Utils; + + public sealed class TagKey : ITagKey + { + public const int MaxLength = 255; + + internal TagKey(string name) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + } + + public string Name { get; } + + public static ITagKey Create(string name) + { + if (!IsValid(name)) + { + throw new ArgumentOutOfRangeException(nameof(name)); + } + + return new TagKey(name); + } + + /// + public override string ToString() + { + return "TagKey{" + + "name=" + this.Name + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is TagKey that) + { + return this.Name.Equals(that.Name); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.Name.GetHashCode(); + return h; + } + + private static bool IsValid(string value) + { + return !string.IsNullOrEmpty(value) && value.Length <= MaxLength && StringUtil.IsPrintableString(value); + } + } +} diff --git a/src/OpenCensus/Tags/TagValue.cs b/src/OpenCensus/Tags/TagValue.cs new file mode 100644 index 000000000..e38ca3fe0 --- /dev/null +++ b/src/OpenCensus/Tags/TagValue.cs @@ -0,0 +1,81 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using System; + using OpenCensus.Utils; + + public sealed class TagValue : ITagValue + { + public const int MaxLength = 255; + + internal TagValue(string asString) + { + this.AsString = asString ?? throw new ArgumentNullException(nameof(asString)); + } + + public string AsString { get; } + + public static ITagValue Create(string value) + { + if (!IsValid(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + return new TagValue(value); + } + + /// + public override string ToString() + { + return "TagValue{" + + "asString=" + this.AsString + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is TagValue that) + { + return this.AsString.Equals(that.AsString); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.AsString.GetHashCode(); + return h; + } + + private static bool IsValid(string value) + { + return value.Length <= MaxLength && StringUtil.IsPrintableString(value); + } + } +} diff --git a/src/OpenCensus/Tags/Tagger.cs b/src/OpenCensus/Tags/Tagger.cs new file mode 100644 index 000000000..dc8d2ef55 --- /dev/null +++ b/src/OpenCensus/Tags/Tagger.cs @@ -0,0 +1,123 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Common; + using OpenCensus.Internal; + + public sealed class Tagger : TaggerBase + { + private readonly CurrentTaggingState state; + + internal Tagger(CurrentTaggingState state) + { + this.state = state; + } + + public override ITagContext Empty + { + get { return TagContext.Empty; } + } + + public override ITagContext CurrentTagContext + { + get + { + return this.state.Internal == TaggingState.DISABLED + ? TagContext.Empty + : ToTagContext(CurrentTagContextUtils.CurrentTagContext); + } + } + + public override ITagContextBuilder EmptyBuilder + { + get + { + return this.state.Internal == TaggingState.DISABLED + ? NoopTagContextBuilder.Instance + : new TagContextBuilder(); + } + } + + public override ITagContextBuilder CurrentBuilder + { + get + { + return this.state.Internal == TaggingState.DISABLED + ? NoopTagContextBuilder.Instance + : this.ToBuilder(CurrentTagContextUtils.CurrentTagContext); + } + } + + public override ITagContextBuilder ToBuilder(ITagContext tags) + { + return this.state.Internal == TaggingState.DISABLED + ? NoopTagContextBuilder.Instance + : ToTagContextBuilder(tags); + } + + public override IScope WithTagContext(ITagContext tags) + { + return this.state.Internal == TaggingState.DISABLED + ? NoopScope.Instance + : CurrentTagContextUtils.WithTagContext(ToTagContext(tags)); + } + + private static ITagContext ToTagContext(ITagContext tags) + { + if (tags is TagContext) + { + return tags; + } +else + { + TagContextBuilder builder = new TagContextBuilder(); + foreach (var tag in tags) + { + if (tag != null) + { + builder.Put(tag.Key, tag.Value); + } + } + + return builder.Build(); + } + } + + private static ITagContextBuilder ToTagContextBuilder(ITagContext tags) + { + // Copy the tags more efficiently in the expected case, when the TagContext is a TagContextImpl. + if (tags is TagContext) + { + return new TagContextBuilder(((TagContext)tags).Tags); + } +else + { + TagContextBuilder builder = new TagContextBuilder(); + foreach (var tag in tags) + { + if (tag != null) + { + builder.Put(tag.Key, tag.Value); + } + } + + return builder; + } + } + } +} diff --git a/src/OpenCensus/Tags/TaggerBase.cs b/src/OpenCensus/Tags/TaggerBase.cs new file mode 100644 index 000000000..66375796d --- /dev/null +++ b/src/OpenCensus/Tags/TaggerBase.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Common; + + public abstract class TaggerBase : ITagger + { + public abstract ITagContext Empty { get; } + + public abstract ITagContext CurrentTagContext { get; } + + public abstract ITagContextBuilder EmptyBuilder { get; } + + public abstract ITagContextBuilder CurrentBuilder { get; } + + public abstract ITagContextBuilder ToBuilder(ITagContext tags); + + public abstract IScope WithTagContext(ITagContext tags); + } +} diff --git a/src/OpenCensus/Tags/Tags.cs b/src/OpenCensus/Tags/Tags.cs new file mode 100644 index 000000000..d577b888b --- /dev/null +++ b/src/OpenCensus/Tags/Tags.cs @@ -0,0 +1,84 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Tags.Propagation; + + public sealed class Tags + { + private static readonly object Lock = new object(); + + private static Tags tags; + + private readonly ITagsComponent tagsComponent = new TagsComponent(); + + internal Tags(bool enabled) + { + if (enabled) + { + this.tagsComponent = new TagsComponent(); + } + else + { + this.tagsComponent = NoopTags.NewNoopTagsComponent(); + } + } + + internal Tags() + : this(false) + { + } + + public static ITagger Tagger + { + get + { + Initialize(true); + return tags.tagsComponent.Tagger; + } + } + + public static ITagPropagationComponent TagPropagationComponent + { + get + { + Initialize(false); + return tags.tagsComponent.TagPropagationComponent; + } + } + + public static TaggingState State + { + get + { + Initialize(false); + return tags.tagsComponent.State; + } + } + + internal static void Initialize(bool enabled) + { + if (tags == null) + { + lock (Lock) + { + tags = tags ?? new Tags(enabled); + } + } + } + } +} diff --git a/src/OpenCensus/Tags/TagsComponent.cs b/src/OpenCensus/Tags/TagsComponent.cs new file mode 100644 index 000000000..caf8ac3d8 --- /dev/null +++ b/src/OpenCensus/Tags/TagsComponent.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Tags.Propagation; + + public class TagsComponent : TagsComponentBase + { + // The TaggingState shared between the TagsComponent, Tagger, and TagPropagationComponent + private readonly CurrentTaggingState state; + private readonly ITagger tagger; + private readonly ITagPropagationComponent tagPropagationComponent; + + public TagsComponent() + { + this.state = new CurrentTaggingState(); + this.tagger = new Tagger(this.state); + this.tagPropagationComponent = new TagPropagationComponent(this.state); + } + + public override ITagger Tagger + { + get { return this.tagger; } + } + + public override ITagPropagationComponent TagPropagationComponent + { + get { return this.tagPropagationComponent; } + } + + public override TaggingState State + { + get { return this.state.Value; } + } + } +} diff --git a/src/OpenCensus/Tags/TagsComponentBase.cs b/src/OpenCensus/Tags/TagsComponentBase.cs new file mode 100644 index 000000000..968f8137d --- /dev/null +++ b/src/OpenCensus/Tags/TagsComponentBase.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags +{ + using OpenCensus.Tags.Propagation; + + public abstract class TagsComponentBase : ITagsComponent + { + public abstract ITagger Tagger { get; } + + public abstract ITagPropagationComponent TagPropagationComponent { get; } + + public abstract TaggingState State { get; } + } +} diff --git a/src/OpenCensus/Tags/Unsafe/AsyncLocalContext.cs b/src/OpenCensus/Tags/Unsafe/AsyncLocalContext.cs new file mode 100644 index 000000000..a34ddb20b --- /dev/null +++ b/src/OpenCensus/Tags/Unsafe/AsyncLocalContext.cs @@ -0,0 +1,61 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Unsafe +{ + using System.Collections.Generic; + using System.Threading; + + internal static class AsyncLocalContext + { + private static readonly ITagContext EmptyTagContextInstance = new EmptyTagContext(); + + private static AsyncLocal context = new AsyncLocal(); + + public static ITagContext CurrentTagContext + { + get + { + if (context.Value == null) + { + return EmptyTagContextInstance; + } + + return context.Value; + } + + set + { + if (value == EmptyTagContextInstance) + { + context.Value = null; + } + else + { + context.Value = value; + } + } + } + + internal sealed class EmptyTagContext : TagContextBase + { + public override IEnumerator GetEnumerator() + { + return new List().GetEnumerator(); + } + } + } +} diff --git a/src/OpenCensus/Trace/Config/NoopTraceConfig.cs b/src/OpenCensus/Trace/Config/NoopTraceConfig.cs new file mode 100644 index 000000000..d1329516a --- /dev/null +++ b/src/OpenCensus/Trace/Config/NoopTraceConfig.cs @@ -0,0 +1,30 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config +{ + public class NoopTraceConfig : TraceConfigBase + { + public override ITraceParams ActiveTraceParams + { + get { return TraceParams.Default; } + } + + public override void UpdateActiveTraceParams(ITraceParams traceParams) + { + } + } +} diff --git a/src/OpenCensus/Trace/Config/TraceConfig.cs b/src/OpenCensus/Trace/Config/TraceConfig.cs new file mode 100644 index 000000000..abf93cd15 --- /dev/null +++ b/src/OpenCensus/Trace/Config/TraceConfig.cs @@ -0,0 +1,41 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config +{ + public sealed class TraceConfig : TraceConfigBase + { + private ITraceParams activeTraceParams; + + public TraceConfig() + { + this.activeTraceParams = TraceParams.Default; + } + + public override ITraceParams ActiveTraceParams + { + get + { + return this.activeTraceParams; + } + } + + public override void UpdateActiveTraceParams(ITraceParams traceParams) + { + this.activeTraceParams = traceParams; + } + } +} diff --git a/src/OpenCensus/Trace/Config/TraceConfigBase.cs b/src/OpenCensus/Trace/Config/TraceConfigBase.cs new file mode 100644 index 000000000..8273667ef --- /dev/null +++ b/src/OpenCensus/Trace/Config/TraceConfigBase.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config +{ + public abstract class TraceConfigBase : ITraceConfig + { + private static readonly NoopTraceConfig NoopTraceConfigInstance = new NoopTraceConfig(); + + public static ITraceConfig NoopTraceConfig + { + get + { + return NoopTraceConfigInstance; + } + } + + public abstract ITraceParams ActiveTraceParams { get; } + + public abstract void UpdateActiveTraceParams(ITraceParams traceParams); + } +} diff --git a/src/OpenCensus/Trace/CurrentSpanUtils.cs b/src/OpenCensus/Trace/CurrentSpanUtils.cs new file mode 100644 index 000000000..97fc03ab7 --- /dev/null +++ b/src/OpenCensus/Trace/CurrentSpanUtils.cs @@ -0,0 +1,70 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System.Threading; + using OpenCensus.Common; + + internal static class CurrentSpanUtils + { + private static AsyncLocal asyncLocalContext = new AsyncLocal(); + + public static ISpan CurrentSpan + { + get + { + return asyncLocalContext.Value; + } + } + + public static IScope WithSpan(ISpan span, bool endSpan) + { + return new ScopeInSpan(span, endSpan); + } + + private sealed class ScopeInSpan : IScope + { + private readonly ISpan origContext; + private readonly ISpan span; + private readonly bool endSpan; + + public ScopeInSpan(ISpan span, bool endSpan) + { + this.span = span; + this.endSpan = endSpan; + this.origContext = asyncLocalContext.Value; + asyncLocalContext.Value = span; + } + + public void Dispose() + { + var current = asyncLocalContext.Value; + asyncLocalContext.Value = this.origContext; + + if (current != this.origContext) + { + // Log + } + + if (this.endSpan) + { + this.span.End(); + } + } + } + } +} diff --git a/src/OpenCensus/Trace/EventWithTime.cs b/src/OpenCensus/Trace/EventWithTime.cs new file mode 100644 index 000000000..571ee10c3 --- /dev/null +++ b/src/OpenCensus/Trace/EventWithTime.cs @@ -0,0 +1,40 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Trace.Export; + + internal class EventWithTime + { + private readonly DateTimeOffset nanoTime; + private readonly T @event; + + public EventWithTime(DateTimeOffset nanoTime, T @event) + { + this.nanoTime = nanoTime; + this.@event = @event; + } + + internal ITimedEvent ToSpanDataTimedEvent(Timer timestampConverter) + { + return TimedEvent.Create(Timestamp.FromDateTimeOffset(this.nanoTime), this.@event); + } + } +} diff --git a/src/OpenCensus/Trace/Export/ExportComponent.cs b/src/OpenCensus/Trace/Export/ExportComponent.cs new file mode 100644 index 000000000..530899e88 --- /dev/null +++ b/src/OpenCensus/Trace/Export/ExportComponent.cs @@ -0,0 +1,58 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using OpenCensus.Common; + using OpenCensus.Internal; + + public sealed class ExportComponent : ExportComponentBase + { + private const int ExporterBufferSize = 32; + + // Enforces that trace export exports data at least once every 5 seconds. + private static readonly Duration ExporterScheduleDelay = Duration.Create(5, 0); + + private ExportComponent(bool supportInProcessStores, IEventQueue eventQueue) + { + this.SpanExporter = Export.SpanExporter.Create(ExporterBufferSize, ExporterScheduleDelay); + this.RunningSpanStore = + supportInProcessStores + ? new InProcessRunningSpanStore() + : Export.RunningSpanStoreBase.NoopRunningSpanStore; + this.SampledSpanStore = + supportInProcessStores + ? new InProcessSampledSpanStore(eventQueue) + : Export.SampledSpanStoreBase.NoopSampledSpanStore; + } + + public override ISpanExporter SpanExporter { get; } + + public override IRunningSpanStore RunningSpanStore { get; } + + public override ISampledSpanStore SampledSpanStore { get; } + + public static IExportComponent CreateWithoutInProcessStores(IEventQueue eventQueue) + { + return new ExportComponent(false, eventQueue); + } + + public static IExportComponent CreateWithInProcessStores(IEventQueue eventQueue) + { + return new ExportComponent(true, eventQueue); + } + } +} diff --git a/src/OpenCensus/Trace/Export/ExportComponentBase.cs b/src/OpenCensus/Trace/Export/ExportComponentBase.cs new file mode 100644 index 000000000..9b2278b80 --- /dev/null +++ b/src/OpenCensus/Trace/Export/ExportComponentBase.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + public abstract class ExportComponentBase : IExportComponent + { + public static IExportComponent NewNoopExportComponent + { + get + { + return new NoopExportComponent(); + } + } + + public abstract ISpanExporter SpanExporter { get; } + + public abstract IRunningSpanStore RunningSpanStore { get; } + + public abstract ISampledSpanStore SampledSpanStore { get; } + } +} diff --git a/src/OpenCensus/Trace/Export/InProcessRunningSpanStore.cs b/src/OpenCensus/Trace/Export/InProcessRunningSpanStore.cs new file mode 100644 index 000000000..dea1ea020 --- /dev/null +++ b/src/OpenCensus/Trace/Export/InProcessRunningSpanStore.cs @@ -0,0 +1,93 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + using OpenCensus.Utils; + + public sealed class InProcessRunningSpanStore : RunningSpanStoreBase + { + private readonly ConcurrentIntrusiveList runningSpans; + + public InProcessRunningSpanStore() + { + this.runningSpans = new ConcurrentIntrusiveList(); + } + + public override IRunningSpanStoreSummary Summary + { + get + { + IEnumerable allRunningSpans = this.runningSpans.Copy(); + Dictionary numSpansPerName = new Dictionary(); + foreach (var span in allRunningSpans) + { + numSpansPerName.TryGetValue(span.Name, out int prevValue); + numSpansPerName[span.Name] = prevValue + 1; + } + + Dictionary perSpanNameSummary = new Dictionary(); + foreach (var it in numSpansPerName) + { + int numRunningSpans = it.Value; + var runningPerSpanNameSummary = RunningPerSpanNameSummary.Create(numRunningSpans); + perSpanNameSummary[it.Key] = runningPerSpanNameSummary; + } + + IRunningSpanStoreSummary summary = RunningSpanStoreSummary.Create(perSpanNameSummary); + return summary; + } + } + + public override IEnumerable GetRunningSpans(IRunningSpanStoreFilter filter) + { + IReadOnlyCollection allRunningSpans = this.runningSpans.Copy(); + int maxSpansToReturn = filter.MaxSpansToReturn == 0 ? allRunningSpans.Count : filter.MaxSpansToReturn; + List ret = new List(maxSpansToReturn); + foreach (var span in allRunningSpans) + { + if (ret.Count == maxSpansToReturn) + { + break; + } + + if (span.Name.Equals(filter.SpanName)) + { + ret.Add(span.ToSpanData()); + } + } + + return ret; + } + + public override void OnEnd(ISpan span) + { + if (span is SpanBase spanBase) + { + this.runningSpans.RemoveElement(spanBase); + } + } + + public override void OnStart(ISpan span) + { + if (span is SpanBase spanBase) + { + this.runningSpans.AddElement(spanBase); + } + } + } +} diff --git a/src/OpenCensus/Trace/Export/InProcessSampledSpanStore.cs b/src/OpenCensus/Trace/Export/InProcessSampledSpanStore.cs new file mode 100644 index 000000000..92e6797a8 --- /dev/null +++ b/src/OpenCensus/Trace/Export/InProcessSampledSpanStore.cs @@ -0,0 +1,433 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Internal; + using OpenCensus.Utils; + + public sealed class InProcessSampledSpanStore : SampledSpanStoreBase + { + private const int NumSampolesPerLatencySamples = 10; + private const int NumSamplesPerErrorSamples = 5; + private static TimeSpan timeBetweenSamples = TimeSpan.FromSeconds(1); + // The total number of canonical codes - 1 (the OK code). + private const int NumErrorBuckets = 17 - 1; // CanonicalCode.values().length - 1; + + private static readonly int NumLatencyBuckets = LatencyBucketBoundaries.Values.Count; + private static readonly int MaxPerSpanNameSamples = + (NumSampolesPerLatencySamples * NumLatencyBuckets) + + (NumSamplesPerErrorSamples * NumErrorBuckets); + + private readonly IEventQueue eventQueue; + private readonly Dictionary samples; + + internal InProcessSampledSpanStore(IEventQueue eventQueue) + { + this.samples = new Dictionary(); + this.eventQueue = eventQueue; + } + + public override ISampledSpanStoreSummary Summary + { + get + { + Dictionary ret = new Dictionary(); + lock (this.samples) + { + foreach (var it in this.samples) + { + ret[it.Key] = SampledPerSpanNameSummary.Create(it.Value.GetNumbersOfLatencySampledSpans(), it.Value.GetNumbersOfErrorSampledSpans()); + } + } + + return SampledSpanStoreSummary.Create(ret); + } + } + + public override ISet RegisteredSpanNamesForCollection + { + get + { + lock (this.samples) + { + return new HashSet(this.samples.Keys); + } + } + } + + public override void ConsiderForSampling(ISpan ispan) + { + if (ispan is SpanBase span) + { + lock (this.samples) + { + string spanName = span.Name; + if (span.IsSampleToLocalSpanStore && !this.samples.ContainsKey(spanName)) + { + this.samples[spanName] = new PerSpanNameSamples(); + } + + this.samples.TryGetValue(spanName, out PerSpanNameSamples perSpanNameSamples); + if (perSpanNameSamples != null) + { + perSpanNameSamples.ConsiderForSampling(span); + } + } + } + } + + public override IEnumerable GetErrorSampledSpans(ISampledSpanStoreErrorFilter filter) + { + int numSpansToReturn = filter.MaxSpansToReturn == 0 ? MaxPerSpanNameSamples : filter.MaxSpansToReturn; + IEnumerable spans = Enumerable.Empty(); + + // Try to not keep the lock to much, do the SpanImpl -> SpanData conversion outside the lock. + lock (this.samples) + { + PerSpanNameSamples perSpanNameSamples = this.samples[filter.SpanName]; + if (perSpanNameSamples != null) + { + spans = perSpanNameSamples.GetErrorSamples(filter.CanonicalCode, numSpansToReturn); + } + } + + List ret = new List(spans.Count()); + foreach (SpanBase span in spans) + { + ret.Add(span.ToSpanData()); + } + + return ret.AsReadOnly(); + } + + public override IEnumerable GetLatencySampledSpans(ISampledSpanStoreLatencyFilter filter) + { + int numSpansToReturn = filter.MaxSpansToReturn == 0 ? MaxPerSpanNameSamples : filter.MaxSpansToReturn; + IEnumerable spans = Enumerable.Empty(); + + // Try to not keep the lock to much, do the SpanImpl -> SpanData conversion outside the lock. + lock (this.samples) + { + PerSpanNameSamples perSpanNameSamples = this.samples[filter.SpanName]; + if (perSpanNameSamples != null) + { + spans = perSpanNameSamples.GetLatencySamples(filter.LatencyLower, filter.LatencyUpper, numSpansToReturn); + } + } + + List ret = new List(spans.Count()); + foreach (SpanBase span in spans) + { + ret.Add(span.ToSpanData()); + } + + return ret.AsReadOnly(); + } + + public override void RegisterSpanNamesForCollection(IEnumerable spanNames) + { + this.eventQueue.Enqueue(new RegisterSpanNameEvent(this, spanNames)); + } + + public override void UnregisterSpanNamesForCollection(IEnumerable spanNames) + { + this.eventQueue.Enqueue(new UnregisterSpanNameEvent(this, spanNames)); + } + + internal void InternalUnregisterSpanNamesForCollection(ICollection spanNames) + { + lock (this.samples) + { + foreach (string spanName in spanNames) + { + this.samples.Remove(spanName); + } + } + } + + internal void InternaltRegisterSpanNamesForCollection(ICollection spanNames) + { + lock (this.samples) + { + foreach (string spanName in spanNames) + { + if (!this.samples.ContainsKey(spanName)) + { + this.samples[spanName] = new PerSpanNameSamples(); + } + } + } + } + + private sealed class Bucket + { + private readonly EvictingQueue sampledSpansQueue; + private readonly EvictingQueue notSampledSpansQueue; + private DateTimeOffset lastSampledTime; + private DateTimeOffset lastNotSampledTime; + + public Bucket(int numSamples) + { + this.sampledSpansQueue = new EvictingQueue(numSamples); + this.notSampledSpansQueue = new EvictingQueue(numSamples); + } + + public static void GetSamples( + int maxSpansToReturn, ICollection output, EvictingQueue queue) + { + SpanBase[] copy = queue.ToArray(); + + foreach (SpanBase span in copy) + { + if (output.Count >= maxSpansToReturn) + { + break; + } + + output.Add(span); + } + } + + public static void GetSamplesFilteredByLatency( + TimeSpan latencyLower, + TimeSpan latencyUpper, + int maxSpansToReturn, + ICollection output, + EvictingQueue queue) + { + SpanBase[] copy = queue.ToArray(); + foreach (SpanBase span in copy) + { + if (output.Count >= maxSpansToReturn) + { + break; + } + + var spanLatency = span.Latency; + if (spanLatency >= latencyLower && spanLatency < latencyUpper) + { + output.Add(span); + } + } + } + + public void ConsiderForSampling(SpanBase span) + { + var spanEndTime = span.EndTime; + if (span.Context.TraceOptions.IsSampled) + { + // Need to compare by doing the subtraction all the time because in case of an overflow, + // this may never sample again (at least for the next ~200 years). No real chance to + // overflow two times because that means the process runs for ~200 years. + if (spanEndTime - this.lastSampledTime > timeBetweenSamples) + { + this.sampledSpansQueue.Add(span); + this.lastSampledTime = spanEndTime; + } + } + else + { + // Need to compare by doing the subtraction all the time because in case of an overflow, + // this may never sample again (at least for the next ~200 years). No real chance to + // overflow two times because that means the process runs for ~200 years. + if (spanEndTime - this.lastNotSampledTime > timeBetweenSamples) + { + this.notSampledSpansQueue.Add(span); + this.lastNotSampledTime = spanEndTime; + } + } + } + + public void GetSamples(int maxSpansToReturn, ICollection output) + { + GetSamples(maxSpansToReturn, output, this.sampledSpansQueue); + GetSamples(maxSpansToReturn, output, this.notSampledSpansQueue); + } + + public void GetSamplesFilteredByLatency( + TimeSpan latencyLower, TimeSpan latencyUpper, int maxSpansToReturn, ICollection output) + { + GetSamplesFilteredByLatency( + latencyLower, latencyUpper, maxSpansToReturn, output, this.sampledSpansQueue); + GetSamplesFilteredByLatency( + latencyLower, latencyUpper, maxSpansToReturn, output, this.notSampledSpansQueue); + } + + public int GetNumSamples() + { + return this.sampledSpansQueue.Count + this.notSampledSpansQueue.Count; + } + } + + private sealed class PerSpanNameSamples + { + private readonly Bucket[] latencyBuckets; + private readonly Bucket[] errorBuckets; + + public PerSpanNameSamples() + { + this.latencyBuckets = new Bucket[NumLatencyBuckets]; + for (int i = 0; i < NumLatencyBuckets; i++) + { + this.latencyBuckets[i] = new Bucket(NumSampolesPerLatencySamples); + } + + this.errorBuckets = new Bucket[NumErrorBuckets]; + for (int i = 0; i < NumErrorBuckets; i++) + { + this.errorBuckets[i] = new Bucket(NumSamplesPerErrorSamples); + } + } + + public Bucket GetLatencyBucket(TimeSpan latency) + { + for (int i = 0; i < NumLatencyBuckets; i++) + { + ISampledLatencyBucketBoundaries boundaries = LatencyBucketBoundaries.Values[i]; + if (latency >= boundaries.LatencyLower + && latency < boundaries.LatencyUpper) + { + return this.latencyBuckets[i]; + } + } + + // latencyNs is negative or Long.MAX_VALUE, so this Span can be ignored. This cannot happen + // in real production because System#nanoTime is monotonic. + return null; + } + + public Bucket GetErrorBucket(CanonicalCode code) + { + return this.errorBuckets[(int)code - 1]; + } + + public void ConsiderForSampling(SpanBase span) + { + Status status = span.Status; + + // Null status means running Span, this should not happen in production, but the library + // should not crash because of this. + if (status != null) + { + Bucket bucket = + status.IsOk + ? this.GetLatencyBucket(span.Latency) + : this.GetErrorBucket(status.CanonicalCode); + + // If unable to find the bucket, ignore this Span. + if (bucket != null) + { + bucket.ConsiderForSampling(span); + } + } + } + + public IDictionary GetNumbersOfLatencySampledSpans() + { + IDictionary latencyBucketSummaries = new Dictionary(); + for (int i = 0; i < NumLatencyBuckets; i++) + { + latencyBucketSummaries[LatencyBucketBoundaries.Values[i]] = this.latencyBuckets[i].GetNumSamples(); + } + + return latencyBucketSummaries; + } + + public IDictionary GetNumbersOfErrorSampledSpans() + { + IDictionary errorBucketSummaries = new Dictionary(); + for (int i = 0; i < NumErrorBuckets; i++) + { + errorBucketSummaries[(CanonicalCode)i + 1] = this.errorBuckets[i].GetNumSamples(); + } + + return errorBucketSummaries; + } + + public IEnumerable GetErrorSamples(CanonicalCode? code, int maxSpansToReturn) + { + List output = new List(maxSpansToReturn); + if (code.HasValue) + { + this.GetErrorBucket(code.Value).GetSamples(maxSpansToReturn, output); + } + else + { + for (int i = 0; i < NumErrorBuckets; i++) + { + this.errorBuckets[i].GetSamples(maxSpansToReturn, output); + } + } + + return output; + } + + public IEnumerable GetLatencySamples(TimeSpan latencyLower, TimeSpan latencyUpper, int maxSpansToReturn) + { + List output = new List(maxSpansToReturn); + for (int i = 0; i < NumLatencyBuckets; i++) + { + ISampledLatencyBucketBoundaries boundaries = LatencyBucketBoundaries.Values[i]; + if (latencyUpper >= boundaries.LatencyLower + && latencyLower < boundaries.LatencyUpper) + { + this.latencyBuckets[i].GetSamplesFilteredByLatency(latencyLower, latencyUpper, maxSpansToReturn, output); + } + } + + return output; + } + } + + private sealed class RegisterSpanNameEvent : IEventQueueEntry + { + private readonly InProcessSampledSpanStore sampledSpanStore; + private readonly ICollection spanNames; + + public RegisterSpanNameEvent(InProcessSampledSpanStore sampledSpanStore, IEnumerable spanNames) + { + this.sampledSpanStore = sampledSpanStore; + this.spanNames = new List(spanNames); + } + + public void Process() + { + this.sampledSpanStore.InternaltRegisterSpanNamesForCollection(this.spanNames); + } + } + + private sealed class UnregisterSpanNameEvent : IEventQueueEntry + { + private readonly InProcessSampledSpanStore sampledSpanStore; + private readonly ICollection spanNames; + + public UnregisterSpanNameEvent(InProcessSampledSpanStore sampledSpanStore, IEnumerable spanNames) + { + this.sampledSpanStore = sampledSpanStore; + this.spanNames = new List(spanNames); + } + + public void Process() + { + this.sampledSpanStore.InternalUnregisterSpanNamesForCollection(this.spanNames); + } + } + } +} diff --git a/src/OpenCensus/Trace/Export/LatencyBucketBoundaries.cs b/src/OpenCensus/Trace/Export/LatencyBucketBoundaries.cs new file mode 100644 index 000000000..03fb6352b --- /dev/null +++ b/src/OpenCensus/Trace/Export/LatencyBucketBoundaries.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + + public class LatencyBucketBoundaries : ISampledLatencyBucketBoundaries + { + public static readonly ISampledLatencyBucketBoundaries ZeroMicrosx10 = new LatencyBucketBoundaries(TimeSpan.Zero, TimeSpan.FromTicks(100)); + public static readonly ISampledLatencyBucketBoundaries Microsx10Microsx100 = new LatencyBucketBoundaries(TimeSpan.FromTicks(100), TimeSpan.FromTicks(1000)); + public static readonly ISampledLatencyBucketBoundaries Microsx100Millix1 = new LatencyBucketBoundaries(TimeSpan.FromTicks(1000), TimeSpan.FromMilliseconds(1)); + public static readonly ISampledLatencyBucketBoundaries Millix1Millix10 = new LatencyBucketBoundaries(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(10)); + public static readonly ISampledLatencyBucketBoundaries Millix10Millix100 = new LatencyBucketBoundaries(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(100)); + public static readonly ISampledLatencyBucketBoundaries Millix100Secondx1 = new LatencyBucketBoundaries(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); + public static readonly ISampledLatencyBucketBoundaries Secondx1Secondx10 = new LatencyBucketBoundaries(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10)); + public static readonly ISampledLatencyBucketBoundaries Secondx10Secondx100 = new LatencyBucketBoundaries(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(100)); + public static readonly ISampledLatencyBucketBoundaries Secondx100Max = new LatencyBucketBoundaries(TimeSpan.FromSeconds(100), TimeSpan.MaxValue); + + public static IReadOnlyList Values = new List + { + ZeroMicrosx10, Microsx10Microsx100, Microsx100Millix1, Millix1Millix10, Millix10Millix100, Millix100Secondx1, Secondx1Secondx10, Secondx10Secondx100, Secondx100Max, + }; + + internal LatencyBucketBoundaries(TimeSpan latencyLowerNs, TimeSpan latencyUpperNs) + { + this.LatencyLower = latencyLowerNs; + this.LatencyUpper = latencyUpperNs; + } + + public TimeSpan LatencyLower { get; } + + public TimeSpan LatencyUpper { get; } + } +} diff --git a/src/OpenCensus/Trace/Export/NoopExportComponent.cs b/src/OpenCensus/Trace/Export/NoopExportComponent.cs new file mode 100644 index 000000000..4f6094bd2 --- /dev/null +++ b/src/OpenCensus/Trace/Export/NoopExportComponent.cs @@ -0,0 +1,47 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + internal sealed class NoopExportComponent : IExportComponent + { + private readonly ISampledSpanStore noopSampledSpanStore = Export.SampledSpanStoreBase.NewNoopSampledSpanStore; + + public ISpanExporter SpanExporter + { + get + { + return Export.SpanExporter.NoopSpanExporter; + } + } + + public IRunningSpanStore RunningSpanStore + { + get + { + return Export.RunningSpanStoreBase.NoopRunningSpanStore; + } + } + + public ISampledSpanStore SampledSpanStore + { + get + { + return this.noopSampledSpanStore; + } + } + } +} diff --git a/src/OpenCensus/Trace/Export/NoopRunningSpanStore.cs b/src/OpenCensus/Trace/Export/NoopRunningSpanStore.cs new file mode 100644 index 000000000..db957132f --- /dev/null +++ b/src/OpenCensus/Trace/Export/NoopRunningSpanStore.cs @@ -0,0 +1,52 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + + internal sealed class NoopRunningSpanStore : RunningSpanStoreBase + { + private static readonly IRunningSpanStoreSummary EmptySummary = RunningSpanStoreSummary.Create(new Dictionary()); + + public override IRunningSpanStoreSummary Summary + { + get + { + return EmptySummary; + } + } + + public override IEnumerable GetRunningSpans(IRunningSpanStoreFilter filter) + { + if (filter == null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return new ISpanData[0]; + } + + public override void OnEnd(ISpan span) + { + } + + public override void OnStart(ISpan span) + { + } + } +} diff --git a/src/OpenCensus/Trace/Export/NoopSampledSpanStore.cs b/src/OpenCensus/Trace/Export/NoopSampledSpanStore.cs new file mode 100644 index 000000000..ad77b7bb6 --- /dev/null +++ b/src/OpenCensus/Trace/Export/NoopSampledSpanStore.cs @@ -0,0 +1,104 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + + internal sealed class NoopSampledSpanStore : SampledSpanStoreBase + { + private static readonly ISampledPerSpanNameSummary EmptyPerSpanNameSummary = SampledPerSpanNameSummary.Create( + new Dictionary(), new Dictionary()); + + private static readonly ISampledSpanStoreSummary EmptySummary = SampledSpanStoreSummary.Create(new Dictionary()); + + private static readonly IEnumerable EmptySpanData = new ISpanData[0]; + + private readonly HashSet registeredSpanNames = new HashSet(); + + public override ISampledSpanStoreSummary Summary + { + get + { + IDictionary result = new Dictionary(); + lock (this.registeredSpanNames) + { + foreach (string registeredSpanName in this.registeredSpanNames) + { + result[registeredSpanName] = EmptyPerSpanNameSummary; + } + } + + return SampledSpanStoreSummary.Create(result); + } + } + + public override ISet RegisteredSpanNamesForCollection + { + get + { + return new HashSet(this.registeredSpanNames); + } + } + + public override void ConsiderForSampling(ISpan span) + { + } + + public override IEnumerable GetErrorSampledSpans(ISampledSpanStoreErrorFilter filter) + { + return EmptySpanData; + } + + public override IEnumerable GetLatencySampledSpans(ISampledSpanStoreLatencyFilter filter) + { + return EmptySpanData; + } + + public override void RegisterSpanNamesForCollection(IEnumerable spanNames) + { + if (spanNames == null) + { + throw new ArgumentNullException(nameof(spanNames)); + } + + lock (this.registeredSpanNames) + { + foreach (var name in spanNames) + { + this.registeredSpanNames.Add(name); + } + } + } + + public override void UnregisterSpanNamesForCollection(IEnumerable spanNames) + { + if (spanNames == null) + { + throw new ArgumentNullException(nameof(spanNames)); + } + + lock (this.registeredSpanNames) + { + foreach (var name in spanNames) + { + this.registeredSpanNames.Remove(name); + } + } + } + } +} diff --git a/src/OpenCensus/Trace/Export/NoopSpanExporter.cs b/src/OpenCensus/Trace/Export/NoopSpanExporter.cs new file mode 100644 index 000000000..ecd1295f2 --- /dev/null +++ b/src/OpenCensus/Trace/Export/NoopSpanExporter.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + internal sealed class NoopSpanExporter : ISpanExporter + { + public void AddSpan(ISpan span) + { + } + + public Task ExportAsync(IEnumerable export, CancellationToken token) + { + return Task.CompletedTask; + } + + public void Dispose() + { + } + + public void RegisterHandler(string name, IHandler handler) + { + } + + public void UnregisterHandler(string name) + { + } + } +} diff --git a/src/OpenCensus/Trace/Export/RunningPerSpanNameSummary.cs b/src/OpenCensus/Trace/Export/RunningPerSpanNameSummary.cs new file mode 100644 index 000000000..30ed2bb29 --- /dev/null +++ b/src/OpenCensus/Trace/Export/RunningPerSpanNameSummary.cs @@ -0,0 +1,73 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + + public sealed class RunningPerSpanNameSummary : IRunningPerSpanNameSummary + { + internal RunningPerSpanNameSummary(int numRunningSpans) + { + this.NumRunningSpans = numRunningSpans; + } + + public int NumRunningSpans { get; } + + public static IRunningPerSpanNameSummary Create(int numRunningSpans) + { + if (numRunningSpans < 0) + { + throw new ArgumentOutOfRangeException("Negative numRunningSpans."); + } + + return new RunningPerSpanNameSummary(numRunningSpans); + } + + /// + public override string ToString() + { + return "RunningPerSpanNameSummary{" + + "numRunningSpans=" + this.NumRunningSpans + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is RunningPerSpanNameSummary that) + { + return this.NumRunningSpans == that.NumRunningSpans; + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.NumRunningSpans; + return h; + } + } +} diff --git a/src/OpenCensus/Trace/Export/RunningSpanStoreBase.cs b/src/OpenCensus/Trace/Export/RunningSpanStoreBase.cs new file mode 100644 index 000000000..b06325e97 --- /dev/null +++ b/src/OpenCensus/Trace/Export/RunningSpanStoreBase.cs @@ -0,0 +1,45 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + public abstract class RunningSpanStoreBase : IRunningSpanStore + { + private static readonly IRunningSpanStore NoopRunningSpanStoreInstance = new NoopRunningSpanStore(); + + protected RunningSpanStoreBase() + { + } + + public abstract IRunningSpanStoreSummary Summary { get; } + + internal static IRunningSpanStore NoopRunningSpanStore + { + get + { + return NoopRunningSpanStoreInstance; + } + } + + public abstract IEnumerable GetRunningSpans(IRunningSpanStoreFilter filter); + + public abstract void OnEnd(ISpan span); + + public abstract void OnStart(ISpan span); + } +} diff --git a/src/OpenCensus/Trace/Export/RunningSpanStoreFilter.cs b/src/OpenCensus/Trace/Export/RunningSpanStoreFilter.cs new file mode 100644 index 000000000..32a31f048 --- /dev/null +++ b/src/OpenCensus/Trace/Export/RunningSpanStoreFilter.cs @@ -0,0 +1,80 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + + public sealed class RunningSpanStoreFilter : IRunningSpanStoreFilter + { + internal RunningSpanStoreFilter(string spanName, int maxSpansToReturn) + { + this.SpanName = spanName ?? throw new ArgumentNullException("Null spanName"); + this.MaxSpansToReturn = maxSpansToReturn; + } + + public string SpanName { get; } + + public int MaxSpansToReturn { get; } + + public static IRunningSpanStoreFilter Create(string spanName, int maxSpansToReturn) + { + if (maxSpansToReturn < 0) + { + throw new ArgumentOutOfRangeException("Negative maxSpansToReturn."); + } + + return new RunningSpanStoreFilter(spanName, maxSpansToReturn); + } + + /// + public override string ToString() + { + return "RunningFilter{" + + "spanName=" + this.SpanName + ", " + + "maxSpansToReturn=" + this.MaxSpansToReturn + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is RunningSpanStoreFilter that) + { + return this.SpanName.Equals(that.SpanName) + && (this.MaxSpansToReturn == that.MaxSpansToReturn); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.SpanName.GetHashCode(); + h *= 1000003; + h ^= this.MaxSpansToReturn; + return h; + } + } +} diff --git a/src/OpenCensus/Trace/Export/RunningSpanStoreSummary.cs b/src/OpenCensus/Trace/Export/RunningSpanStoreSummary.cs new file mode 100644 index 000000000..8be0eef2a --- /dev/null +++ b/src/OpenCensus/Trace/Export/RunningSpanStoreSummary.cs @@ -0,0 +1,77 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + public sealed class RunningSpanStoreSummary : IRunningSpanStoreSummary + { + internal RunningSpanStoreSummary(IDictionary perSpanNameSummary) + { + this.PerSpanNameSummary = perSpanNameSummary; + } + + public IDictionary PerSpanNameSummary { get; } + + public static IRunningSpanStoreSummary Create(IDictionary perSpanNameSummary) + { + if (perSpanNameSummary == null) + { + throw new ArgumentNullException(nameof(perSpanNameSummary)); + } + + IDictionary copy = new Dictionary(perSpanNameSummary); + return new RunningSpanStoreSummary(new ReadOnlyDictionary(copy)); + } + + /// + public override string ToString() + { + return "RunningSummary{" + + "perSpanNameSummary=" + this.PerSpanNameSummary + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is RunningSpanStoreSummary that) + { + return this.PerSpanNameSummary.SequenceEqual(that.PerSpanNameSummary); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.PerSpanNameSummary.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Trace/Export/SampledPerSpanNameSummary.cs b/src/OpenCensus/Trace/Export/SampledPerSpanNameSummary.cs new file mode 100644 index 000000000..5e50762ef --- /dev/null +++ b/src/OpenCensus/Trace/Export/SampledPerSpanNameSummary.cs @@ -0,0 +1,90 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + public sealed class SampledPerSpanNameSummary : ISampledPerSpanNameSummary + { + internal SampledPerSpanNameSummary(IDictionary numbersOfLatencySampledSpans, IDictionary numbersOfErrorSampledSpans) + { + this.NumbersOfLatencySampledSpans = numbersOfLatencySampledSpans ?? throw new ArgumentNullException(nameof(numbersOfLatencySampledSpans)); + this.NumbersOfErrorSampledSpans = numbersOfErrorSampledSpans ?? throw new ArgumentNullException(nameof(numbersOfErrorSampledSpans)); + } + + public IDictionary NumbersOfLatencySampledSpans { get; } + + public IDictionary NumbersOfErrorSampledSpans { get; } + + public static ISampledPerSpanNameSummary Create(IDictionary numbersOfLatencySampledSpans, IDictionary numbersOfErrorSampledSpans) + { + if (numbersOfLatencySampledSpans == null) + { + throw new ArgumentNullException(nameof(numbersOfLatencySampledSpans)); + } + + if (numbersOfErrorSampledSpans == null) + { + throw new ArgumentNullException(nameof(numbersOfErrorSampledSpans)); + } + + IDictionary copy1 = new Dictionary(numbersOfLatencySampledSpans); + IDictionary copy2 = new Dictionary(numbersOfErrorSampledSpans); + return new SampledPerSpanNameSummary(new ReadOnlyDictionary(copy1), new ReadOnlyDictionary(copy2)); + } + + /// + public override string ToString() + { + return "SampledPerSpanNameSummary{" + + "numbersOfLatencySampledSpans=" + this.NumbersOfLatencySampledSpans + ", " + + "numbersOfErrorSampledSpans=" + this.NumbersOfErrorSampledSpans + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is SampledPerSpanNameSummary that) + { + return this.NumbersOfLatencySampledSpans.SequenceEqual(that.NumbersOfLatencySampledSpans) + && this.NumbersOfErrorSampledSpans.SequenceEqual(that.NumbersOfErrorSampledSpans); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.NumbersOfLatencySampledSpans.GetHashCode(); + h *= 1000003; + h ^= this.NumbersOfErrorSampledSpans.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Trace/Export/SampledSpanStoreBase.cs b/src/OpenCensus/Trace/Export/SampledSpanStoreBase.cs new file mode 100644 index 000000000..76838e32f --- /dev/null +++ b/src/OpenCensus/Trace/Export/SampledSpanStoreBase.cs @@ -0,0 +1,59 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + + public abstract class SampledSpanStoreBase : ISampledSpanStore + { + private static readonly ISampledSpanStore NoopSampledSpanStoreInstance = new NoopSampledSpanStore(); + + protected SampledSpanStoreBase() + { + } + + public abstract ISampledSpanStoreSummary Summary { get; } + + public abstract ISet RegisteredSpanNamesForCollection { get; } + + internal static ISampledSpanStore NoopSampledSpanStore + { + get + { + return NoopSampledSpanStoreInstance; + } + } + + internal static ISampledSpanStore NewNoopSampledSpanStore + { + get + { + return new NoopSampledSpanStore(); + } + } + + public abstract void ConsiderForSampling(ISpan span); + + public abstract IEnumerable GetErrorSampledSpans(ISampledSpanStoreErrorFilter filter); + + public abstract IEnumerable GetLatencySampledSpans(ISampledSpanStoreLatencyFilter filter); + + public abstract void RegisterSpanNamesForCollection(IEnumerable spanNames); + + public abstract void UnregisterSpanNamesForCollection(IEnumerable spanNames); + } +} diff --git a/src/OpenCensus/Trace/Export/SampledSpanStoreErrorFilter.cs b/src/OpenCensus/Trace/Export/SampledSpanStoreErrorFilter.cs new file mode 100644 index 000000000..926ddbee3 --- /dev/null +++ b/src/OpenCensus/Trace/Export/SampledSpanStoreErrorFilter.cs @@ -0,0 +1,92 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + + public sealed class SampledSpanStoreErrorFilter : ISampledSpanStoreErrorFilter + { + internal SampledSpanStoreErrorFilter(string spanName, CanonicalCode? canonicalCode, int maxSpansToReturn) + { + this.SpanName = spanName ?? throw new ArgumentNullException(nameof(spanName)); + this.CanonicalCode = canonicalCode; + this.MaxSpansToReturn = maxSpansToReturn; + } + + public string SpanName { get; } + + public CanonicalCode? CanonicalCode { get; } + + public int MaxSpansToReturn { get; } + + public static ISampledSpanStoreErrorFilter Create(string spanName, CanonicalCode? canonicalCode, int maxSpansToReturn) + { + if (canonicalCode == Trace.CanonicalCode.Ok) + { + throw new ArgumentOutOfRangeException("Invalid canonical code."); + } + + if (maxSpansToReturn < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxSpansToReturn)); + } + + return new SampledSpanStoreErrorFilter(spanName, canonicalCode, maxSpansToReturn); + } + + /// + public override string ToString() + { + return "ErrorFilter{" + + "spanName=" + this.SpanName + ", " + + "canonicalCode=" + this.CanonicalCode + ", " + + "maxSpansToReturn=" + this.MaxSpansToReturn + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is SampledSpanStoreErrorFilter that) + { + return this.SpanName.Equals(that.SpanName) + && (this.CanonicalCode == that.CanonicalCode) + && (this.MaxSpansToReturn == that.MaxSpansToReturn); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.SpanName.GetHashCode(); + h *= 1000003; + h ^= this.CanonicalCode.GetHashCode(); + h *= 1000003; + h ^= this.MaxSpansToReturn; + return h; + } + } +} diff --git a/src/OpenCensus/Trace/Export/SampledSpanStoreLatencyFilter.cs b/src/OpenCensus/Trace/Export/SampledSpanStoreLatencyFilter.cs new file mode 100644 index 000000000..1905e5fb8 --- /dev/null +++ b/src/OpenCensus/Trace/Export/SampledSpanStoreLatencyFilter.cs @@ -0,0 +1,104 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + + public sealed class SampledSpanStoreLatencyFilter : ISampledSpanStoreLatencyFilter + { + internal SampledSpanStoreLatencyFilter(string spanName, TimeSpan latencyLowerNs, TimeSpan latencyUpperNs, int maxSpansToReturn) + { + this.SpanName = spanName ?? throw new ArgumentNullException(nameof(spanName)); + this.LatencyLower = latencyLowerNs; + this.LatencyUpper = latencyUpperNs; + this.MaxSpansToReturn = maxSpansToReturn; + } + + public string SpanName { get; } + + public TimeSpan LatencyLower { get; } + + public TimeSpan LatencyUpper { get; } + + public int MaxSpansToReturn { get; } + + public static ISampledSpanStoreLatencyFilter Create(string spanName, TimeSpan latencyLower, TimeSpan latencyUpper, int maxSpansToReturn) + { + if (maxSpansToReturn < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxSpansToReturn)); + } + + if (latencyLower < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(latencyLower)); + } + + if (latencyUpper < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(latencyUpper)); + } + + return new SampledSpanStoreLatencyFilter(spanName, latencyLower, latencyUpper, maxSpansToReturn); + } + + /// + public override string ToString() + { + return "LatencyFilter{" + + "spanName=" + this.SpanName + ", " + + "latencyLowerNs=" + this.LatencyLower + ", " + + "latencyUpperNs=" + this.LatencyUpper + ", " + + "maxSpansToReturn=" + this.MaxSpansToReturn + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is SampledSpanStoreLatencyFilter that) + { + return this.SpanName.Equals(that.SpanName) + && (this.LatencyLower == that.LatencyLower) + && (this.LatencyUpper == that.LatencyUpper) + && (this.MaxSpansToReturn == that.MaxSpansToReturn); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= this.SpanName.GetHashCode(); + h *= 1000003; + h ^= (this.LatencyLower.Ticks >> 32) ^ this.LatencyLower.Ticks; + h *= 1000003; + h ^= (this.LatencyUpper.Ticks >> 32) ^ this.LatencyUpper.Ticks; + h *= 1000003; + h ^= this.MaxSpansToReturn; + return (int)h; + } + } +} diff --git a/src/OpenCensus/Trace/Export/SampledSpanStoreSummary.cs b/src/OpenCensus/Trace/Export/SampledSpanStoreSummary.cs new file mode 100644 index 000000000..962b53a21 --- /dev/null +++ b/src/OpenCensus/Trace/Export/SampledSpanStoreSummary.cs @@ -0,0 +1,77 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + public class SampledSpanStoreSummary : ISampledSpanStoreSummary + { + internal SampledSpanStoreSummary(IDictionary perSpanNameSummary) + { + this.PerSpanNameSummary = perSpanNameSummary ?? throw new ArgumentNullException(nameof(perSpanNameSummary)); + } + + public IDictionary PerSpanNameSummary { get; } + + public static ISampledSpanStoreSummary Create(IDictionary perSpanNameSummary) + { + if (perSpanNameSummary == null) + { + throw new ArgumentNullException(nameof(perSpanNameSummary)); + } + + IDictionary copy = new Dictionary(perSpanNameSummary); + return new SampledSpanStoreSummary(new ReadOnlyDictionary(copy)); + } + + /// + public override string ToString() + { + return "SampledSummary{" + + "perSpanNameSummary=" + this.PerSpanNameSummary + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is SampledSpanStoreSummary that) + { + return this.PerSpanNameSummary.SequenceEqual(that.PerSpanNameSummary); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.PerSpanNameSummary.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Trace/Export/SpanExporter.cs b/src/OpenCensus/Trace/Export/SpanExporter.cs new file mode 100644 index 000000000..2e97e5ab6 --- /dev/null +++ b/src/OpenCensus/Trace/Export/SpanExporter.cs @@ -0,0 +1,83 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using OpenCensus.Common; + + public sealed class SpanExporter : SpanExporterBase + { + private readonly Thread workerThread; + + private readonly SpanExporterWorker worker; + + internal SpanExporter(SpanExporterWorker worker) + { + this.worker = worker; + this.workerThread = new Thread(worker.Run) + { + IsBackground = true, + Name = "SpanExporter", + }; + this.workerThread.Start(); + } + + internal Thread ServiceExporterThread + { + get + { + return this.workerThread; + } + } + + public override void AddSpan(ISpan span) + { + this.worker.AddSpan(span); + } + + /// + public override Task ExportAsync(IEnumerable export, CancellationToken token) + { + this.worker.ExportAsync(export, token); + + return Task.CompletedTask; + } + + public override void RegisterHandler(string name, IHandler handler) + { + this.worker.RegisterHandler(name, handler); + } + + public override void UnregisterHandler(string name) + { + this.worker.UnregisterHandler(name); + } + + public override void Dispose() + { + this.worker.Dispose(); + } + + internal static ISpanExporter Create(int bufferSize, Duration scheduleDelay) + { + SpanExporterWorker worker = new SpanExporterWorker(bufferSize, scheduleDelay); + return new SpanExporter(worker); + } + } +} diff --git a/src/OpenCensus/Trace/Export/SpanExporterBase.cs b/src/OpenCensus/Trace/Export/SpanExporterBase.cs new file mode 100644 index 000000000..aac029461 --- /dev/null +++ b/src/OpenCensus/Trace/Export/SpanExporterBase.cs @@ -0,0 +1,46 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + public abstract class SpanExporterBase : ISpanExporter + { + private static readonly ISpanExporter NoopSpanExporterInstance = new NoopSpanExporter(); + + public static ISpanExporter NoopSpanExporter + { + get + { + return NoopSpanExporterInstance; + } + } + + public abstract void AddSpan(ISpan span); + + /// + public abstract Task ExportAsync(IEnumerable export, CancellationToken token); + + public abstract void Dispose(); + + public abstract void RegisterHandler(string name, IHandler handler); + + public abstract void UnregisterHandler(string name); + } +} diff --git a/src/OpenCensus/Trace/Export/SpanExporterWorker.cs b/src/OpenCensus/Trace/Export/SpanExporterWorker.cs new file mode 100644 index 000000000..ce2a1eb59 --- /dev/null +++ b/src/OpenCensus/Trace/Export/SpanExporterWorker.cs @@ -0,0 +1,151 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using OpenCensus.Common; + using OpenCensus.Implementation; + + internal class SpanExporterWorker : IDisposable + { + private readonly int bufferSize; + private readonly BlockingCollection spans; + private readonly ConcurrentDictionary serviceHandlers = new ConcurrentDictionary(); + private readonly TimeSpan scheduleDelay; + private bool shutdown = false; + + public SpanExporterWorker(int bufferSize, Duration scheduleDelay) + { + this.bufferSize = bufferSize; + this.scheduleDelay = TimeSpan.FromSeconds(scheduleDelay.Seconds); + this.spans = new BlockingCollection(); + } + + public void Dispose() + { + this.shutdown = true; + this.spans.CompleteAdding(); + } + + internal void AddSpan(ISpan span) + { + if (!this.spans.IsAddingCompleted) + { + if (!this.spans.TryAdd(span)) + { + // Log failure, dropped span + } + } + } + + internal async Task ExportAsync(IEnumerable export, CancellationToken token) + { + var handlers = this.serviceHandlers.Values; + foreach (var handler in handlers) + { + try + { + // TODO: the async handlers could be run in parallel. + await handler.ExportAsync(export); + } + catch (Exception ex) + { + OpenCensusEventSource.Log.ExporterThrownExceptionWarning(ex); + } + } + } + + internal async void Run(object obj) + { + List toExport = new List(); + while (!this.shutdown) + { + try + { + if (this.spans.TryTake(out ISpan item, this.scheduleDelay)) + { + // Build up list + this.BuildList(item, toExport); + + // Export them + await this.ExportAsync(toExport, CancellationToken.None); + + // Get ready for next batch + toExport.Clear(); + } + + if (this.spans.IsCompleted) + { + break; + } + } + catch (Exception) + { + // Log + return; + } + } + } + + internal void RegisterHandler(string name, IHandler handler) + { + this.serviceHandlers[name] = handler; + } + + internal void UnregisterHandler(string name) + { + this.serviceHandlers.TryRemove(name, out IHandler prev); + } + + internal ISpanData ToSpanData(ISpan span) + { + if (!(span is Span spanImpl)) + { + throw new InvalidOperationException("ISpan not a Span"); + } + + return spanImpl.ToSpanData(); + } + + private void BuildList(ISpan item, ICollection toExport) + { + if (item is Span span) + { + toExport.Add(span.ToSpanData()); + } + + // Grab as many as we can + while (this.spans.TryTake(out item)) + { + span = item as Span; + if (span != null) + { + toExport.Add(span.ToSpanData()); + } + + if (toExport.Count >= this.bufferSize) + { + break; + } + } + } + } +} diff --git a/src/OpenCensus/Trace/Export/SpanExtensions.cs b/src/OpenCensus/Trace/Export/SpanExtensions.cs new file mode 100644 index 000000000..97e25a005 --- /dev/null +++ b/src/OpenCensus/Trace/Export/SpanExtensions.cs @@ -0,0 +1,22 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export +{ + internal static class SpanExtensions + { + } +} diff --git a/src/OpenCensus/Trace/Internal/BlankSpan.cs b/src/OpenCensus/Trace/Internal/BlankSpan.cs new file mode 100644 index 000000000..44aa2aadc --- /dev/null +++ b/src/OpenCensus/Trace/Internal/BlankSpan.cs @@ -0,0 +1,112 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Internal +{ + using System; + using System.Collections.Generic; + using OpenCensus.Trace.Export; + + internal sealed class BlankSpan : SpanBase + { + public static readonly BlankSpan Instance = new BlankSpan(); + + private BlankSpan() + : base(SpanContext.Invalid, default(SpanOptions)) + { + } + + /// + public override string Name { get; set; } + + public override Status Status { get; set; } + + public override SpanKind? Kind { get; set; } + + public override DateTimeOffset EndTime + { + get + { + return DateTimeOffset.MinValue; + } + } + + public override TimeSpan Latency + { + get + { + return TimeSpan.Zero; + } + } + + public override bool IsSampleToLocalSpanStore + { + get + { + return false; + } + } + + public override ISpanId ParentSpanId + { + get + { + return null; + } + } + + public override bool HasEnded => true; + + public override void PutAttributes(IDictionary attributes) + { + } + + public override void PutAttribute(string key, IAttributeValue value) + { + } + + public override void AddAnnotation(string description, IDictionary attributes) + { + } + + public override void AddAnnotation(IAnnotation annotation) + { + } + + public override void AddMessageEvent(IMessageEvent messageEvent) + { + } + + public override void AddLink(ILink link) + { + } + + public override void End(EndSpanOptions options) + { + } + + /// + public override string ToString() + { + return "BlankSpan"; + } + + public override ISpanData ToSpanData() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/OpenCensus/Trace/Internal/RandomGenerator.cs b/src/OpenCensus/Trace/Internal/RandomGenerator.cs new file mode 100644 index 000000000..80a690dd9 --- /dev/null +++ b/src/OpenCensus/Trace/Internal/RandomGenerator.cs @@ -0,0 +1,57 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Internal +{ + using System; + + internal class RandomGenerator : IRandomGenerator + { + private static readonly Random Global = new Random(); + + [ThreadStatic] + private static Random local; + + private readonly int seed; + private readonly bool sameSeed; + + internal RandomGenerator() + { + this.sameSeed = false; + } + + /// + /// This constructur uses the same seed for all the thread static random objects. + /// You might get the same values if a random is accessed from different threads. + /// Use only for unit tests... + /// + internal RandomGenerator(int seed) + { + this.sameSeed = true; + this.seed = seed; + } + + public void NextBytes(byte[] bytes) + { + if (local == null) + { + local = new Random(this.sameSeed ? this.seed : Global.Next()); + } + + local.NextBytes(bytes); + } + } +} diff --git a/src/OpenCensus/Trace/Internal/StartEndHandler.cs b/src/OpenCensus/Trace/Internal/StartEndHandler.cs new file mode 100644 index 000000000..a8d654fed --- /dev/null +++ b/src/OpenCensus/Trace/Internal/StartEndHandler.cs @@ -0,0 +1,117 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Internal +{ + using OpenCensus.Internal; + using OpenCensus.Trace.Export; + + internal sealed class StartEndHandler : IStartEndHandler + { + private readonly ISpanExporter spanExporter; + private readonly IRunningSpanStore runningSpanStore; + private readonly ISampledSpanStore sampledSpanStore; + private readonly IEventQueue eventQueue; + + // true if any of (runningSpanStore OR sampledSpanStore) are different than null, which + // means the spans with RECORD_EVENTS should be enqueued in the queue. + private readonly bool enqueueEventForNonSampledSpans; + + public StartEndHandler(ISpanExporter spanExporter, IRunningSpanStore runningSpanStore, ISampledSpanStore sampledSpanStore, IEventQueue eventQueue) + { + this.spanExporter = spanExporter; + this.runningSpanStore = runningSpanStore; + this.sampledSpanStore = sampledSpanStore; + this.enqueueEventForNonSampledSpans = runningSpanStore != null || sampledSpanStore != null; + this.eventQueue = eventQueue; + } + + public void OnEnd(ISpan span) + { + if ((span.Options.HasFlag(SpanOptions.RecordEvents) && this.enqueueEventForNonSampledSpans) + || span.Context.TraceOptions.IsSampled) + { + this.eventQueue.Enqueue(new SpanEndEvent(span, this.spanExporter, this.runningSpanStore, this.sampledSpanStore)); + } + } + + public void OnStart(ISpan span) + { + if (span.Options.HasFlag(SpanOptions.RecordEvents) && this.enqueueEventForNonSampledSpans) + { + this.eventQueue.Enqueue(new SpanStartEvent(span, this.runningSpanStore)); + } + } + + private sealed class SpanStartEvent : IEventQueueEntry + { + private readonly ISpan span; + private readonly IRunningSpanStore activeSpansExporter; + + public SpanStartEvent(ISpan span, IRunningSpanStore activeSpansExporter) + { + this.span = span; + this.activeSpansExporter = activeSpansExporter; + } + + public void Process() + { + if (this.activeSpansExporter != null) + { + this.activeSpansExporter.OnStart(this.span); + } + } + } + + private sealed class SpanEndEvent : IEventQueueEntry + { + private readonly ISpan span; + private readonly IRunningSpanStore runningSpanStore; + private readonly ISpanExporter spanExporter; + private readonly ISampledSpanStore sampledSpanStore; + + public SpanEndEvent( + ISpan span, + ISpanExporter spanExporter, + IRunningSpanStore runningSpanStore, + ISampledSpanStore sampledSpanStore) + { + this.span = span; + this.runningSpanStore = runningSpanStore; + this.spanExporter = spanExporter; + this.sampledSpanStore = sampledSpanStore; + } + + public void Process() + { + if (this.span.Context.TraceOptions.IsSampled) + { + this.spanExporter.AddSpan(this.span); + } + + if (this.runningSpanStore != null) + { + this.runningSpanStore.OnEnd(this.span); + } + + if (this.sampledSpanStore != null) + { + this.sampledSpanStore.ConsiderForSampling(this.span); + } + } + } + } +} diff --git a/src/OpenCensus/Trace/Link.cs b/src/OpenCensus/Trace/Link.cs new file mode 100644 index 000000000..1027d82b9 --- /dev/null +++ b/src/OpenCensus/Trace/Link.cs @@ -0,0 +1,105 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using OpenCensus.Utils; + + public sealed class Link : ILink + { + private static readonly IDictionary EmptyAttributes = new Dictionary(); + + private Link(ITraceId traceId, ISpanId spanId, LinkType type, IDictionary attributes) + { + this.TraceId = traceId ?? throw new ArgumentNullException(nameof(traceId)); + this.SpanId = spanId ?? throw new ArgumentNullException(nameof(spanId)); + this.Type = type; + this.Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes)); + } + + public ITraceId TraceId { get; } + + public ISpanId SpanId { get; } + + public LinkType Type { get; } + + public IDictionary Attributes { get; } + + public static ILink FromSpanContext(ISpanContext context, LinkType type) + { + return new Link(context.TraceId, context.SpanId, type, EmptyAttributes); + } + + public static ILink FromSpanContext(ISpanContext context, LinkType type, IDictionary attributes) + { + IDictionary copy = new Dictionary(attributes); + return new Link( + context.TraceId, + context.SpanId, + type, + new ReadOnlyDictionary(copy)); + } + + /// + public override string ToString() + { + return "Link{" + + "traceId=" + this.TraceId + ", " + + "spanId=" + this.SpanId + ", " + + "type=" + this.Type + ", " + + "attributes=" + Collections.ToString(this.Attributes) + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is Link that) + { + return this.TraceId.Equals(that.TraceId) + && this.SpanId.Equals(that.SpanId) + && this.Type.Equals(that.Type) + && this.Attributes.SequenceEqual(that.Attributes); + } + + return false; + } + + /// + public override int GetHashCode() + { + int h = 1; + h *= 1000003; + h ^= this.TraceId.GetHashCode(); + h *= 1000003; + h ^= this.SpanId.GetHashCode(); + h *= 1000003; + h ^= this.Type.GetHashCode(); + h *= 1000003; + h ^= this.Attributes.GetHashCode(); + return h; + } + } +} diff --git a/src/OpenCensus/Trace/MessageEvent.cs b/src/OpenCensus/Trace/MessageEvent.cs new file mode 100644 index 000000000..d3df917e5 --- /dev/null +++ b/src/OpenCensus/Trace/MessageEvent.cs @@ -0,0 +1,94 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + public class MessageEvent : IMessageEvent + { + internal MessageEvent(MessageEventType type, long messageId, long uncompressedMessageSize, long compressedMessageSize) + { + this.Type = type; + this.MessageId = messageId; + this.UncompressedMessageSize = uncompressedMessageSize; + this.CompressedMessageSize = compressedMessageSize; + } + + public MessageEventType Type { get; } + + public long MessageId { get; } + + public long UncompressedMessageSize { get; } + + public long CompressedMessageSize { get; } + + public static MessageEventBuilder Builder(MessageEventType type, long messageId) + { + return new MessageEventBuilder() + .SetType(type) + .SetMessageId(messageId) + + // We need to set a value for the message size because the autovalue requires all + // primitives to be initialized. + .SetUncompressedMessageSize(0) + .SetCompressedMessageSize(0); + } + + /// + public override string ToString() + { + return "MessageEvent{" + + "type=" + this.Type + ", " + + "messageId=" + this.MessageId + ", " + + "uncompressedMessageSize=" + this.UncompressedMessageSize + ", " + + "compressedMessageSize=" + this.CompressedMessageSize + + "}"; + } + + /// + public override bool Equals(object o) + { + if (o == this) + { + return true; + } + + if (o is MessageEvent that) + { + return this.Type.Equals(that.Type) + && (this.MessageId == that.MessageId) + && (this.UncompressedMessageSize == that.UncompressedMessageSize) + && (this.CompressedMessageSize == that.CompressedMessageSize); + } + + return false; + } + + /// + public override int GetHashCode() + { + long h = 1; + h *= 1000003; + h ^= this.Type.GetHashCode(); + h *= 1000003; + h ^= (this.MessageId >> 32) ^ this.MessageId; + h *= 1000003; + h ^= (this.UncompressedMessageSize >> 32) ^ this.UncompressedMessageSize; + h *= 1000003; + h ^= (this.CompressedMessageSize >> 32) ^ this.CompressedMessageSize; + return (int)h; + } + } +} diff --git a/src/OpenCensus/Trace/MessageEventBuilder.cs b/src/OpenCensus/Trace/MessageEventBuilder.cs new file mode 100644 index 000000000..cacfed315 --- /dev/null +++ b/src/OpenCensus/Trace/MessageEventBuilder.cs @@ -0,0 +1,103 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + + public class MessageEventBuilder + { + private MessageEventType? type; + private long? messageId; + private long? uncompressedMessageSize; + private long? compressedMessageSize; + + internal MessageEventBuilder() + { + } + + internal MessageEventBuilder( + MessageEventType type, + long messageId, + long uncompressedMessageSize, + long compressedMessageSize) + { + this.type = type; + this.messageId = messageId; + this.uncompressedMessageSize = uncompressedMessageSize; + this.compressedMessageSize = compressedMessageSize; + } + + public MessageEventBuilder SetUncompressedMessageSize(long uncompressedMessageSize) + { + this.uncompressedMessageSize = uncompressedMessageSize; + return this; + } + + public MessageEventBuilder SetCompressedMessageSize(long compressedMessageSize) + { + this.compressedMessageSize = compressedMessageSize; + return this; + } + + public IMessageEvent Build() + { + string missing = string.Empty; + if (!this.type.HasValue) + { + missing += " type"; + } + + if (!this.messageId.HasValue) + { + missing += " messageId"; + } + + if (!this.uncompressedMessageSize.HasValue) + { + missing += " uncompressedMessageSize"; + } + + if (!this.compressedMessageSize.HasValue) + { + missing += " compressedMessageSize"; + } + + if (!string.IsNullOrEmpty(missing)) + { + throw new ArgumentOutOfRangeException("Missing required properties:" + missing); + } + + return new MessageEvent( + this.type.Value, + this.messageId.Value, + this.uncompressedMessageSize.Value, + this.compressedMessageSize.Value); + } + + internal MessageEventBuilder SetType(MessageEventType type) + { + this.type = type; + return this; + } + + internal MessageEventBuilder SetMessageId(long messageId) + { + this.messageId = messageId; + return this; + } + } +} diff --git a/src/OpenCensus/Trace/NoopSpanBuilder.cs b/src/OpenCensus/Trace/NoopSpanBuilder.cs new file mode 100644 index 000000000..a262e6400 --- /dev/null +++ b/src/OpenCensus/Trace/NoopSpanBuilder.cs @@ -0,0 +1,63 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using OpenCensus.Trace.Internal; + + public class NoopSpanBuilder : SpanBuilderBase + { + private NoopSpanBuilder(string name, SpanKind kind) : base(kind) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + } + + public override ISpan StartSpan() + { + return BlankSpan.Instance; + } + + public override ISpanBuilder SetSampler(ISampler sampler) + { + return this; + } + + public override ISpanBuilder SetParentLinks(IEnumerable parentLinks) + { + return this; + } + + public override ISpanBuilder SetRecordEvents(bool recordEvents) + { + return this; + } + + internal static ISpanBuilder CreateWithParent(string spanName, SpanKind kind, ISpan parent = null) + { + return new NoopSpanBuilder(spanName, kind); + } + + internal static ISpanBuilder CreateWithRemoteParent(string spanName, SpanKind kind, ISpanContext remoteParentSpanContext = null) + { + return new NoopSpanBuilder(spanName, kind); + } + } +} diff --git a/src/OpenCensus/Trace/NoopTraceComponent.cs b/src/OpenCensus/Trace/NoopTraceComponent.cs new file mode 100644 index 000000000..a68141c67 --- /dev/null +++ b/src/OpenCensus/Trace/NoopTraceComponent.cs @@ -0,0 +1,59 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Propagation; + + internal sealed class NoopTraceComponent : ITraceComponent + { + private readonly IExportComponent noopExportComponent = Export.ExportComponentBase.NewNoopExportComponent; + + public ITracer Tracer + { + get + { + return Trace.Tracer.NoopTracer; + } + } + + public IPropagationComponent PropagationComponent + { + get + { + return Propagation.PropagationComponentBase.NoopPropagationComponent; + } + } + + public IExportComponent ExportComponent + { + get + { + return this.noopExportComponent; + } + } + + public ITraceConfig TraceConfig + { + get + { + return Config.TraceConfigBase.NoopTraceConfig; + } + } + } +} diff --git a/src/OpenCensus/Trace/NoopTracer.cs b/src/OpenCensus/Trace/NoopTracer.cs new file mode 100644 index 000000000..2baeddce0 --- /dev/null +++ b/src/OpenCensus/Trace/NoopTracer.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + public sealed class NoopTracer : TracerBase, ITracer + { + internal NoopTracer() + { + } + + public override ISpanBuilder SpanBuilderWithExplicitParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpan parent = null) + { + return NoopSpanBuilder.CreateWithParent(spanName, spanKind, parent); + } + + public override ISpanBuilder SpanBuilderWithRemoteParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpanContext remoteParentSpanContext = null) + { + return NoopSpanBuilder.CreateWithRemoteParent(spanName, spanKind, remoteParentSpanContext); + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/B3Format.cs b/src/OpenCensus/Trace/Propagation/B3Format.cs new file mode 100644 index 000000000..1a1fc28a1 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/B3Format.cs @@ -0,0 +1,139 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// B3 text propagator. See https://github.com/openzipkin/b3-propagation for the specification. + /// + public sealed class B3Format : TextFormatBase + { + internal const string XB3TraceId = "X-B3-TraceId"; + internal const string XB3SpanId = "X-B3-SpanId"; + internal const string XB3ParentSpanId = "X-B3-ParentSpanId"; + internal const string XB3Sampled = "X-B3-Sampled"; + internal const string XB3Flags = "X-B3-Flags"; + + // Used as the upper TraceId.SIZE hex characters of the traceID. B3-propagation used to send + // TraceId.SIZE hex characters (8-bytes traceId) in the past. + internal const string UpperTraceId = "0000000000000000"; + + // Sampled value via the X_B3_SAMPLED header. + internal const string SampledValue = "1"; + + // "Debug" sampled value. + internal const string FlagsValue = "1"; + + private static readonly HashSet AllFields = new HashSet() { XB3TraceId, XB3SpanId, XB3ParentSpanId, XB3Sampled, XB3Flags }; + + /// + public override ISet Fields + { + get + { + return AllFields; + } + } + + /// + public override ISpanContext Extract(T carrier, Func> getter) + { + if (carrier == null) + { + throw new ArgumentNullException(nameof(carrier)); + } + + if (getter == null) + { + throw new ArgumentNullException(nameof(getter)); + } + + try + { + ITraceId traceId; + string traceIdStr = getter(carrier, XB3TraceId)?.FirstOrDefault(); + if (traceIdStr != null) + { + if (traceIdStr.Length == TraceId.Size) + { + // This is an 8-byte traceID. + traceIdStr = UpperTraceId + traceIdStr; + } + + traceId = TraceId.FromLowerBase16(traceIdStr); + } + else + { + throw new SpanContextParseException("Missing X_B3_TRACE_ID."); + } + + ISpanId spanId; + string spanIdStr = getter(carrier, XB3SpanId)?.FirstOrDefault(); + if (spanIdStr != null) + { + spanId = SpanId.FromLowerBase16(spanIdStr); + } + else + { + throw new SpanContextParseException("Missing X_B3_SPAN_ID."); + } + + TraceOptions traceOptions = TraceOptions.Default; + if (SampledValue.Equals(getter(carrier, XB3Sampled)?.FirstOrDefault()) + || FlagsValue.Equals(getter(carrier, XB3Flags)?.FirstOrDefault())) + { + traceOptions = TraceOptions.Builder().SetIsSampled(true).Build(); + } + + return SpanContext.Create(traceId, spanId, traceOptions, Tracestate.Empty); + } + catch (Exception e) + { + throw new SpanContextParseException("Invalid input.", e); + } + } + + /// + public override void Inject(ISpanContext spanContext, T carrier, Action setter) + { + if (spanContext == null) + { + throw new ArgumentNullException(nameof(spanContext)); + } + + if (carrier == null) + { + throw new ArgumentNullException(nameof(carrier)); + } + + if (setter == null) + { + throw new ArgumentNullException(nameof(setter)); + } + + setter(carrier, XB3TraceId, spanContext.TraceId.ToLowerBase16()); + setter(carrier, XB3SpanId, spanContext.SpanId.ToLowerBase16()); + if (spanContext.TraceOptions.IsSampled) + { + setter(carrier, XB3Sampled, SampledValue); + } + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/BinaryFormatBase.cs b/src/OpenCensus/Trace/Propagation/BinaryFormatBase.cs new file mode 100644 index 000000000..5fa135d40 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/BinaryFormatBase.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using OpenCensus.Trace.Propagation.Implementation; + + public abstract class BinaryFormatBase : IBinaryFormat + { + internal static readonly NoopBinaryFormat NoopBinaryFormatInstance = new NoopBinaryFormat(); + + internal static IBinaryFormat NoopBinaryFormat + { + get + { + return NoopBinaryFormatInstance; + } + } + + public abstract ISpanContext FromByteArray(byte[] bytes); + + public abstract byte[] ToByteArray(ISpanContext spanContext); + } +} diff --git a/src/OpenCensus/Trace/Propagation/DefaultPropagationComponent.cs b/src/OpenCensus/Trace/Propagation/DefaultPropagationComponent.cs new file mode 100644 index 000000000..2920bb5f4 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/DefaultPropagationComponent.cs @@ -0,0 +1,47 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using OpenCensus.Trace.Propagation.Implementation; + + /// + /// Default propagation used by Open Census. + /// + public sealed class DefaultPropagationComponent : PropagationComponentBase + { + private readonly BinaryFormat binaryFormat = new BinaryFormat(); + private readonly TraceContextFormat textFormat = new TraceContextFormat(); + + /// + public override IBinaryFormat BinaryFormat + { + get + { + return this.binaryFormat; + } + } + + /// + public override ITextFormat TextFormat + { + get + { + return this.textFormat; + } + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/Implementation/BinaryFormat.cs b/src/OpenCensus/Trace/Propagation/Implementation/BinaryFormat.cs new file mode 100644 index 000000000..510ec6568 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/Implementation/BinaryFormat.cs @@ -0,0 +1,101 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Implementation +{ + using System; + + internal class BinaryFormat : BinaryFormatBase + { + private const byte VersionId = 0; + private const int VersionIdOffset = 0; + + // The version_id/field_id size in bytes. + private const byte IdSize = 1; + private const byte TraceIdFieldId = 0; + private const int TraceIdFieldIdOffset = VersionIdOffset + IdSize; + private const int TraceIdOffset = TraceIdFieldIdOffset + IdSize; + private const byte SpanIdFieldId = 1; + private const int SpaneIdFieldIdOffset = TraceIdOffset + TraceId.Size; + private const int SpanIdOffset = SpaneIdFieldIdOffset + IdSize; + private const byte TraceOptionsFieldId = 2; + private const int TraceOptionFieldIdOffset = SpanIdOffset + SpanId.Size; + private const int TraceOptionOffset = TraceOptionFieldIdOffset + IdSize; + private const int FormatLength = (4 * IdSize) + TraceId.Size + SpanId.Size + TraceOptions.Size; + + public override ISpanContext FromByteArray(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + if (bytes.Length == 0 || bytes[0] != VersionId) + { + throw new SpanContextParseException("Unsupported version."); + } + + ITraceId traceId = TraceId.Invalid; + ISpanId spanId = SpanId.Invalid; + TraceOptions traceOptions = TraceOptions.Default; + + int pos = 1; + try + { + if (bytes.Length > pos && bytes[pos] == TraceIdFieldId) + { + traceId = TraceId.FromBytes(bytes, pos + IdSize); + pos += IdSize + TraceId.Size; + } + + if (bytes.Length > pos && bytes[pos] == SpanIdFieldId) + { + spanId = SpanId.FromBytes(bytes, pos + IdSize); + pos += IdSize + SpanId.Size; + } + + if (bytes.Length > pos && bytes[pos] == TraceOptionsFieldId) + { + traceOptions = TraceOptions.FromBytes(bytes, pos + IdSize); + } + + return SpanContext.Create(traceId, spanId, traceOptions, Tracestate.Empty); + } + catch (Exception e) + { + throw new SpanContextParseException("Invalid input.", e); + } + } + + public override byte[] ToByteArray(ISpanContext spanContext) + { + if (spanContext == null) + { + throw new ArgumentNullException(nameof(spanContext)); + } + + byte[] bytes = new byte[FormatLength]; + bytes[VersionIdOffset] = VersionId; + bytes[TraceIdFieldIdOffset] = TraceIdFieldId; + spanContext.TraceId.CopyBytesTo(bytes, TraceIdOffset); + bytes[SpaneIdFieldIdOffset] = SpanIdFieldId; + spanContext.SpanId.CopyBytesTo(bytes, SpanIdOffset); + bytes[TraceOptionFieldIdOffset] = TraceOptionsFieldId; + spanContext.TraceOptions.CopyBytesTo(bytes, TraceOptionOffset); + return bytes; + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/Implementation/NoopBinaryFormat.cs b/src/OpenCensus/Trace/Propagation/Implementation/NoopBinaryFormat.cs new file mode 100644 index 000000000..57c2c0d1f --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/Implementation/NoopBinaryFormat.cs @@ -0,0 +1,43 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Implementation +{ + using System; + + internal class NoopBinaryFormat : IBinaryFormat + { + public ISpanContext FromByteArray(byte[] bytes) + { + if (bytes == null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + return SpanContext.Invalid; + } + + public byte[] ToByteArray(ISpanContext spanContext) + { + if (spanContext == null) + { + throw new ArgumentNullException(nameof(spanContext)); + } + + return new byte[0]; + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/Implementation/NoopPropagationComponent.cs b/src/OpenCensus/Trace/Propagation/Implementation/NoopPropagationComponent.cs new file mode 100644 index 000000000..00797300a --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/Implementation/NoopPropagationComponent.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Implementation +{ + internal class NoopPropagationComponent : IPropagationComponent + { + public IBinaryFormat BinaryFormat + { + get + { + return Propagation.BinaryFormatBase.NoopBinaryFormat; + } + } + + public ITextFormat TextFormat + { + get + { + return Propagation.TextFormatBase.NoopTextFormat; + } + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/Implementation/NoopTextFormat.cs b/src/OpenCensus/Trace/Propagation/Implementation/NoopTextFormat.cs new file mode 100644 index 000000000..864de8f11 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/Implementation/NoopTextFormat.cs @@ -0,0 +1,63 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Implementation +{ + using System; + using System.Collections.Generic; + + internal class NoopTextFormat : TextFormatBase + { + internal NoopTextFormat() + { + } + + public override ISet Fields => new HashSet(); + + public override void Inject(ISpanContext spanContext, T carrier, Action setter) + { + if (spanContext == null) + { + throw new ArgumentNullException(nameof(spanContext)); + } + + if (carrier == null) + { + throw new ArgumentNullException(nameof(carrier)); + } + + if (setter == null) + { + throw new ArgumentNullException(nameof(setter)); + } + } + + public override ISpanContext Extract(T carrier, Func> getter) + { + if (carrier == null) + { + throw new ArgumentNullException(nameof(carrier)); + } + + if (getter == null) + { + throw new ArgumentNullException(nameof(getter)); + } + + return SpanContext.Invalid; + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/PropagationComponentBase.cs b/src/OpenCensus/Trace/Propagation/PropagationComponentBase.cs new file mode 100644 index 000000000..1d7c78a57 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/PropagationComponentBase.cs @@ -0,0 +1,37 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using OpenCensus.Trace.Propagation.Implementation; + + public abstract class PropagationComponentBase : IPropagationComponent + { + private static readonly IPropagationComponent NoopPropagationComponentInstance = new NoopPropagationComponent(); + + public static IPropagationComponent NoopPropagationComponent + { + get + { + return NoopPropagationComponentInstance; + } + } + + public abstract IBinaryFormat BinaryFormat { get; } + + public abstract ITextFormat TextFormat { get; } + } +} diff --git a/src/OpenCensus/Trace/Propagation/SpanContextParseException.cs b/src/OpenCensus/Trace/Propagation/SpanContextParseException.cs new file mode 100644 index 000000000..0ba60b123 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/SpanContextParseException.cs @@ -0,0 +1,33 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using System; + + public class SpanContextParseException : Exception + { + public SpanContextParseException(string message) + : base(message) + { + } + + public SpanContextParseException(string message, Exception cause) + : base(message, cause) + { + } + } +} diff --git a/src/OpenCensus/Trace/Propagation/TextFormatBase.cs b/src/OpenCensus/Trace/Propagation/TextFormatBase.cs new file mode 100644 index 000000000..c03fc98fe --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/TextFormatBase.cs @@ -0,0 +1,48 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using System; + using System.Collections.Generic; + using OpenCensus.Trace.Propagation.Implementation; + + /// + /// Text format wire context propagator. Helps to extract and inject context from textual + /// representation (typically http headers or metadata colleciton). + /// + public abstract class TextFormatBase : ITextFormat + { + private static readonly NoopTextFormat NoopTextFormatInstance = new NoopTextFormat(); + + /// + public abstract ISet Fields { get; } + + internal static ITextFormat NoopTextFormat + { + get + { + return NoopTextFormatInstance; + } + } + + /// + public abstract ISpanContext Extract(T carrier, Func> getter); + + /// + public abstract void Inject(ISpanContext spanContext, T carrier, Action setter); + } +} diff --git a/src/OpenCensus/Trace/Propagation/TraceContextFormat.cs b/src/OpenCensus/Trace/Propagation/TraceContextFormat.cs new file mode 100644 index 000000000..33395a945 --- /dev/null +++ b/src/OpenCensus/Trace/Propagation/TraceContextFormat.cs @@ -0,0 +1,300 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using OpenCensus.Utils; + + /// + /// W3C trace context text wire protocol formatter. See https://github.com/w3c/distributed-tracing/. + /// + public class TraceContextFormat : TextFormatBase + { + private static readonly int VersionLength = "00".Length; + private static readonly int VersionPrefixIdLength = "00-".Length; + private static readonly int TraceIdLength = "0af7651916cd43dd8448eb211c80319c".Length; + private static readonly int VersionAndTraceIdLength = "00-0af7651916cd43dd8448eb211c80319c-".Length; + private static readonly int SpanIdLength = "00f067aa0ba902b7".Length; + private static readonly int VersionAndTraceIdAndSpanIdLength = "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-".Length; + private static readonly int OptionsLength = "00".Length; + + /// + public override ISet Fields => new HashSet { "tracestate", "traceparent" }; + + /// + public override ISpanContext Extract(T carrier, Func> getter) + { + try + { + var traceparentCollection = getter(carrier, "traceparent"); + var tracestateCollection = getter(carrier, "tracestate"); + + if (traceparentCollection.Count() > 1) + { + // multiple traceparent are not allowed + return null; + } + + var traceparent = traceparentCollection?.FirstOrDefault(); + var traceparentParsed = this.TryExtractTraceparent(traceparent, out ITraceId traceId, out ISpanId spanId, out TraceOptions traceoptions); + + if (!traceparentParsed) + { + return null; + } + + var tracestateResult = Tracestate.Empty; + try + { + List> entries = new List>(); + HashSet names = new HashSet(); + var discardTracestate = false; + if (tracestateCollection != null) + { + foreach (var tracestate in tracestateCollection) + { + if (string.IsNullOrWhiteSpace(tracestate)) + { + continue; + } + + // tracestate: rojo=00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01,congo=BleGNlZWRzIHRohbCBwbGVhc3VyZS4 + var keyStartIdx = 0; + var length = tracestate.Length; + while (keyStartIdx < length) + { + // first skip any prefix commas and OWS + var c = tracestate[keyStartIdx]; + while (c == ' ' || c == '\t' || c == ',') + { + keyStartIdx++; + if (keyStartIdx == length) + { + break; + } + + c = tracestate[keyStartIdx]; + } + + if (keyStartIdx == length) + { + break; + } + + var keyEndIdx = tracestate.IndexOf("=", keyStartIdx); + + if (keyEndIdx == -1) + { + discardTracestate = true; + break; + } + + var valueStartIdx = keyEndIdx + 1; + + var valueEndIdx = tracestate.IndexOf(",", valueStartIdx); + valueEndIdx = valueEndIdx == -1 ? length : valueEndIdx; + + // this will throw for duplicated keys + var key = tracestate.Substring(keyStartIdx, keyEndIdx - keyStartIdx).TrimStart(); + if (names.Add(key)) + { + entries.Add( + new KeyValuePair( + key, + tracestate.Substring(valueStartIdx, valueEndIdx - valueStartIdx).TrimEnd())); + } + else + { + discardTracestate = true; + break; + } + + keyStartIdx = valueEndIdx + 1; + } + } + } + + if (!discardTracestate) + { + var tracestateBuilder = Tracestate.Builder; + + entries.Reverse(); + foreach (var entry in entries) + { + tracestateBuilder.Set(entry.Key, entry.Value); + } + + tracestateResult = tracestateBuilder.Build(); + } + } + catch (Exception ex) + { + // failure to parse tracestate should not disregard traceparent + // TODO: logging + } + + return SpanContext.Create(traceId, spanId, traceoptions, tracestateResult); + } + catch (Exception ex) + { + // TODO: logging + } + + // in case of exception indicate to upstream that there is no parseable context from the top + return null; + } + + /// + public override void Inject(ISpanContext spanContext, T carrier, Action setter) + { + var traceparent = string.Concat("00-", spanContext.TraceId.ToLowerBase16(), "-", spanContext.SpanId.ToLowerBase16()); + traceparent = string.Concat(traceparent, spanContext.TraceOptions.IsSampled ? "-01" : "-00"); + + setter(carrier, "traceparent", traceparent); + + StringBuilder sb = new StringBuilder(); + var isFirst = true; + + foreach (var entry in spanContext.Tracestate.Entries) + { + if (isFirst) + { + isFirst = false; + } + else + { + sb.Append(","); + } + + sb.Append(entry.Key).Append("=").Append(entry.Value); + } + + if (sb.Length > 0) + { + setter(carrier, "tracestate", sb.ToString()); + } + } + + private bool TryExtractTraceparent(string traceparent, out ITraceId traceId, out ISpanId spanId, out TraceOptions traceoptions) + { + // from https://github.com/w3c/distributed-tracing/blob/master/trace_context/HTTP_HEADER_FORMAT.md + // traceparent: 00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01 + + traceId = TraceId.Invalid; + spanId = SpanId.Invalid; + traceoptions = TraceOptions.Default; + var bestAttempt = false; + + if (string.IsNullOrWhiteSpace(traceparent)) + { + return false; + } + + // if version does not end with delimeter + if (traceparent.Length < VersionPrefixIdLength || traceparent[VersionPrefixIdLength - 1] != '-') + { + return false; + } + + // or version is not a hex (will throw) + var versionArray = Arrays.StringToByteArray(traceparent, 0, VersionLength); + + if (versionArray[0] == 255) + { + return false; + } + + if (versionArray[0] > 0) + { + // expected version is 00 + // for higher versions - best attempt parsing of trace id, span id, etc. + bestAttempt = true; + } + + if (traceparent.Length < VersionAndTraceIdLength || traceparent[VersionAndTraceIdLength - 1] != '-') + { + return false; + } + + try + { + traceId = TraceId.FromBytes(Arrays.StringToByteArray(traceparent, VersionPrefixIdLength, TraceIdLength)); + } + catch (ArgumentOutOfRangeException) + { + // it's ok to still parse tracestate + return false; + } + + if (traceparent.Length < VersionAndTraceIdAndSpanIdLength || traceparent[VersionAndTraceIdAndSpanIdLength - 1] != '-') + { + return false; + } + + try + { + spanId = SpanId.FromBytes(Arrays.StringToByteArray(traceparent, VersionAndTraceIdLength, SpanIdLength)); + } + catch (ArgumentOutOfRangeException) + { + // it's ok to still parse tracestate + return false; + } + + if (traceparent.Length < VersionAndTraceIdAndSpanIdLength + OptionsLength) + { + return false; + } + + byte[] optionsArray; + + try + { + optionsArray = Arrays.StringToByteArray(traceparent, VersionAndTraceIdAndSpanIdLength, OptionsLength); + } + catch (ArgumentOutOfRangeException) + { + // it's ok to still parse tracestate + return false; + } + + if ((optionsArray[0] | 1) == 1) + { + traceoptions = TraceOptions.Builder().SetIsSampled(true).Build(); + } + + if ((!bestAttempt) && (traceparent.Length != VersionAndTraceIdAndSpanIdLength + OptionsLength)) + { + return false; + } + + if (bestAttempt) + { + if ((traceparent.Length > VersionAndTraceIdAndSpanIdLength + OptionsLength) && + (traceparent[VersionAndTraceIdAndSpanIdLength + OptionsLength] != '-')) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/OpenCensus/Trace/Span.cs b/src/OpenCensus/Trace/Span.cs new file mode 100644 index 000000000..c40359cea --- /dev/null +++ b/src/OpenCensus/Trace/Span.cs @@ -0,0 +1,482 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Utils; + + public sealed class Span : SpanBase + { + private readonly ISpanId parentSpanId; + private readonly bool? hasRemoteParent; + private readonly ITraceParams traceParams; + private readonly IStartEndHandler startEndHandler; + private readonly Timer timestampConverter; + private readonly DateTimeOffset startTime; + private readonly object @lock = new object(); + private AttributesWithCapacity attributes; + private TraceEvents> annotations; + private TraceEvents> messageEvents; + private TraceEvents links; + private Status status; + private DateTimeOffset endTime; + private bool hasBeenEnded; + private bool sampleToLocalSpanStore; + + private Span( + ISpanContext context, + SpanOptions options, + string name, + ISpanId parentSpanId, + bool? hasRemoteParent, + ITraceParams traceParams, + IStartEndHandler startEndHandler, + Timer timestampConverter) + : base(context, options) + { + this.parentSpanId = parentSpanId; + this.hasRemoteParent = hasRemoteParent; + this.Name = name; + this.traceParams = traceParams ?? throw new ArgumentNullException(nameof(traceParams)); + this.startEndHandler = startEndHandler; + this.hasBeenEnded = false; + this.sampleToLocalSpanStore = false; + if (options.HasFlag(SpanOptions.RecordEvents)) + { + if (timestampConverter == null) + { + this.timestampConverter = Timer.StartNew(); + this.startTime = this.timestampConverter.StartTime; + } + else + { + this.timestampConverter = timestampConverter; + this.startTime = this.timestampConverter.Now; + } + } + else + { + this.startTime = DateTimeOffset.MinValue; + this.timestampConverter = timestampConverter; + } + } + + /// + public override string Name { get; set; } + + public override Status Status + { + get + { + lock (this.@lock) + { + return this.StatusWithDefault; + } + } + + set + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling setStatus() on an ended Span."); + return; + } + + this.status = value; + } + } + } + + public override SpanKind? Kind + { + get; + + set; // TODO: do we need to notify when attempt to set on already closed Span? + } + + public override DateTimeOffset EndTime + { + get + { + lock (this.@lock) + { + return this.hasBeenEnded ? this.endTime : this.timestampConverter.Now; + } + } + } + + public override TimeSpan Latency + { + get + { + lock (this.@lock) + { + return this.hasBeenEnded ? this.endTime - this.startTime : this.timestampConverter.Now - this.startTime; + } + } + } + + public override bool IsSampleToLocalSpanStore + { + get + { + lock (this.@lock) + { + if (!this.hasBeenEnded) + { + throw new InvalidOperationException("Running span does not have the SampleToLocalSpanStore set."); + } + + return this.sampleToLocalSpanStore; + } + } + } + + public override ISpanId ParentSpanId + { + get + { + return this.parentSpanId; + } + } + + public override bool HasEnded + { + get + { + return this.hasBeenEnded; + } + } + + internal Timer TimestampConverter + { + get + { + return this.timestampConverter; + } + } + + private AttributesWithCapacity InitializedAttributes + { + get + { + if (this.attributes == null) + { + this.attributes = new AttributesWithCapacity(this.traceParams.MaxNumberOfAttributes); + } + + return this.attributes; + } + } + + private TraceEvents> InitializedAnnotations + { + get + { + if (this.annotations == null) + { + this.annotations = + new TraceEvents>(this.traceParams.MaxNumberOfAnnotations); + } + + return this.annotations; + } + } + + private TraceEvents> InitializedMessageEvents + { + get + { + if (this.messageEvents == null) + { + this.messageEvents = + new TraceEvents>(this.traceParams.MaxNumberOfMessageEvents); + } + + return this.messageEvents; + } + } + + private TraceEvents InitializedLinks + { + get + { + if (this.links == null) + { + this.links = new TraceEvents(this.traceParams.MaxNumberOfLinks); + } + + return this.links; + } + } + + private Status StatusWithDefault + { + get + { + return this.status ?? Trace.Status.Ok; + } + } + + public override void PutAttribute(string key, IAttributeValue value) + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling putAttributes() on an ended Span."); + return; + } + + this.InitializedAttributes.PutAttribute(key, value); + } + } + + public override void PutAttributes(IDictionary attributes) + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling putAttributes() on an ended Span."); + return; + } + + this.InitializedAttributes.PutAttributes(attributes); + } + } + + public override void AddAnnotation(string description, IDictionary attributes) + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling addAnnotation() on an ended Span."); + return; + } + + this.InitializedAnnotations.AddEvent(new EventWithTime(this.timestampConverter.Now, Annotation.FromDescriptionAndAttributes(description, attributes))); + } + } + + public override void AddAnnotation(IAnnotation annotation) + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling addAnnotation() on an ended Span."); + return; + } + + if (annotation == null) + { + throw new ArgumentNullException(nameof(annotation)); + } + + this.InitializedAnnotations.AddEvent(new EventWithTime(this.timestampConverter.Now, annotation)); + } + } + + public override void AddLink(ILink link) + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling addLink() on an ended Span."); + return; + } + + if (link == null) + { + throw new ArgumentNullException(nameof(link)); + } + + this.InitializedLinks.AddEvent(link); + } + } + + public override void AddMessageEvent(IMessageEvent messageEvent) + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling addNetworkEvent() on an ended Span."); + return; + } + + if (messageEvent == null) + { + throw new ArgumentNullException(nameof(messageEvent)); + } + + this.InitializedMessageEvents.AddEvent(new EventWithTime(this.timestampConverter.Now, messageEvent)); + } + } + + public override void End(EndSpanOptions options) + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + return; + } + + lock (this.@lock) + { + if (this.hasBeenEnded) + { + // logger.log(Level.FINE, "Calling end() on an ended Span."); + return; + } + + if (options.Status != null) + { + this.status = options.Status; + } + + this.sampleToLocalSpanStore = options.SampleToLocalSpanStore; + this.endTime = this.timestampConverter.Now; + this.hasBeenEnded = true; + } + + this.startEndHandler.OnEnd(this); + } + + public override ISpanData ToSpanData() + { + if (!this.Options.HasFlag(SpanOptions.RecordEvents)) + { + throw new InvalidOperationException("Getting SpanData for a Span without RECORD_EVENTS option."); + } + + Attributes attributesSpanData = this.attributes == null ? Attributes.Create(new Dictionary(), 0) + : Attributes.Create(this.attributes, this.attributes.NumberOfDroppedAttributes); + + ITimedEvents annotationsSpanData = CreateTimedEvents(this.InitializedAnnotations, this.timestampConverter); + ITimedEvents messageEventsSpanData = CreateTimedEvents(this.InitializedMessageEvents, this.timestampConverter); + LinkList linksSpanData = this.links == null ? LinkList.Create(new List(), 0) : LinkList.Create(this.links.Events, this.links.NumberOfDroppedEvents); + + return SpanData.Create( + this.Context, + this.parentSpanId, + this.hasRemoteParent, + this.Name, + Timestamp.FromDateTimeOffset(this.startTime), + attributesSpanData, + annotationsSpanData, + messageEventsSpanData, + linksSpanData, + null, // Not supported yet. + this.hasBeenEnded ? this.StatusWithDefault : null, + this.Kind.HasValue ? this.Kind.Value : SpanKind.Client, + this.hasBeenEnded ? Timestamp.FromDateTimeOffset(this.endTime) : null); + } + + internal static ISpan StartSpan( + ISpanContext context, + SpanOptions options, + string name, + ISpanId parentSpanId, + bool? hasRemoteParent, + ITraceParams traceParams, + IStartEndHandler startEndHandler, + Timer timestampConverter) + { + var span = new Span( + context, + options, + name, + parentSpanId, + hasRemoteParent, + traceParams, + startEndHandler, + timestampConverter); + + // Call onStart here instead of calling in the constructor to make sure the span is completely + // initialized. + if (span.Options.HasFlag(SpanOptions.RecordEvents)) + { + startEndHandler.OnStart(span); + } + + return span; + } + + private static ITimedEvents CreateTimedEvents(TraceEvents> events, Timer timestampConverter) + { + if (events == null) + { + IEnumerable> empty = new ITimedEvent[0]; + return TimedEvents.Create(empty, 0); + } + + var eventsList = new List>(events.Events.Count); + foreach (EventWithTime networkEvent in events.Events) + { + eventsList.Add(networkEvent.ToSpanDataTimedEvent(timestampConverter)); + } + + return TimedEvents.Create(eventsList, events.NumberOfDroppedEvents); + } + } +} diff --git a/src/OpenCensus/Trace/SpanBase.cs b/src/OpenCensus/Trace/SpanBase.cs new file mode 100644 index 000000000..bff200c1e --- /dev/null +++ b/src/OpenCensus/Trace/SpanBase.cs @@ -0,0 +1,151 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using OpenCensus.Trace.Export; + using OpenCensus.Utils; + + /// + /// Span base class. + /// + public abstract class SpanBase : ISpan, IElement + { + private static readonly IDictionary EmptyAttributes = new Dictionary(); + + internal SpanBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Span context. + /// Span creation options. + protected SpanBase(ISpanContext context, SpanOptions options = SpanOptions.None) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.TraceOptions.IsSampled && !options.HasFlag(SpanOptions.RecordEvents)) + { + throw new ArgumentOutOfRangeException("Span is sampled, but does not have RECORD_EVENTS set."); + } + + this.Context = context; + this.Options = options; + } + + public abstract string Name { get; set; } + + /// + public virtual ISpanContext Context { get; } + + /// + public virtual SpanOptions Options { get; } + + /// + public abstract Status Status { get; set; } + + /// + public abstract SpanKind? Kind { get; set; } + + /// + public SpanBase Next { get; set; } + + /// + public SpanBase Previous { get; set; } + + /// + /// Gets the span end time. + /// + public abstract DateTimeOffset EndTime { get; } + + /// + /// Gets the latency (difference beteen stat and end time). + /// + public abstract TimeSpan Latency { get; } + + /// + /// Gets a value indicating whether span stored in local store. + /// + public abstract bool IsSampleToLocalSpanStore { get; } + + /// + /// Gets the parent span id. + /// + public abstract ISpanId ParentSpanId { get; } + + /// + /// Gets a value indicating whether this span was already stopped. + /// + public abstract bool HasEnded { get; } + + /// + public virtual void PutAttribute(string key, IAttributeValue value) + { + this.PutAttributes(new Dictionary() { { key, value } }); + } + + /// + public abstract void PutAttributes(IDictionary attributes); + + /// + public void AddAnnotation(string description) + { + this.AddAnnotation(description, EmptyAttributes); + } + + /// + public abstract void AddAnnotation(string description, IDictionary attributes); + + /// + public abstract void AddAnnotation(IAnnotation annotation); + + /// + public abstract void AddMessageEvent(IMessageEvent messageEvent); + + /// + public abstract void AddLink(ILink link); + + /// + public abstract void End(EndSpanOptions options); + + /// + public void End() + { + this.End(EndSpanOptions.Default); + } + + /// + public override string ToString() + { + return "Span[" + + this.Name + + "]"; + } + + /// + /// Converts this span into span data for exporting purposes. + /// + /// Span Data corresponding current span. + public abstract ISpanData ToSpanData(); + } +} diff --git a/src/OpenCensus/Trace/SpanBuilder.cs b/src/OpenCensus/Trace/SpanBuilder.cs new file mode 100644 index 000000000..dfdc2d247 --- /dev/null +++ b/src/OpenCensus/Trace/SpanBuilder.cs @@ -0,0 +1,235 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Internal; + using OpenCensus.Trace.Config; + + public class SpanBuilder : SpanBuilderBase + { + private SpanBuilder(string name, SpanKind kind, SpanBuilderOptions options, ISpanContext remoteParentSpanContext = null, ISpan parent = null) : base(kind) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Parent = parent; + this.RemoteParentSpanContext = remoteParentSpanContext; + this.Options = options; + } + + private SpanBuilderOptions Options { get; set; } + + private string Name { get; set; } + + private ISpan Parent { get; set; } + + private ISpanContext RemoteParentSpanContext { get; set; } + + private ISampler Sampler { get; set; } + + private IEnumerable ParentLinks { get; set; } = Enumerable.Empty(); + + private bool RecordEvents { get; set; } + + public override ISpan StartSpan() + { + ISpanContext parentContext = this.RemoteParentSpanContext; + bool hasRemoteParent = true; + Timer timestampConverter = null; + if (this.RemoteParentSpanContext == null) + { + // This is not a child of a remote Span. Get the parent SpanContext from the parent Span if + // any. + ISpan parent = this.Parent; + hasRemoteParent = false; + if (parent != null) + { + parentContext = parent.Context; + + // Pass the timestamp converter from the parent to ensure that the recorded events are in + // the right order. Implementation uses System.nanoTime() which is monotonically increasing. + if (parent is Span) + { + timestampConverter = ((Span)parent).TimestampConverter; + } + } + else + { + hasRemoteParent = false; + } + } + + return this.StartSpanInternal( + parentContext, + hasRemoteParent, + this.Name, + this.Sampler, + this.ParentLinks, + this.RecordEvents, + timestampConverter); + } + + public override ISpanBuilder SetSampler(ISampler sampler) + { + this.Sampler = sampler ?? throw new ArgumentNullException(nameof(sampler)); + return this; + } + + public override ISpanBuilder SetParentLinks(IEnumerable parentLinks) + { + this.ParentLinks = parentLinks ?? throw new ArgumentNullException(nameof(parentLinks)); + return this; + } + + public override ISpanBuilder SetRecordEvents(bool recordEvents) + { + this.RecordEvents = recordEvents; + return this; + } + + internal static ISpanBuilder CreateWithParent(string spanName, SpanKind kind, ISpan parent, SpanBuilderOptions options) + { + return new SpanBuilder(spanName, kind, options, null, parent); + } + + internal static ISpanBuilder CreateWithRemoteParent(string spanName, SpanKind kind, ISpanContext remoteParentSpanContext, SpanBuilderOptions options) + { + return new SpanBuilder(spanName, kind, options, remoteParentSpanContext, null); + } + + private static bool IsAnyParentLinkSampled(IEnumerable parentLinks) + { + foreach (ISpan parentLink in parentLinks) + { + if (parentLink.Context.TraceOptions.IsSampled) + { + return true; + } + } + + return false; + } + + private static void LinkSpans(ISpan span, IEnumerable parentLinks) + { + if (parentLinks.Any()) + { + ILink childLink = Link.FromSpanContext(span.Context, LinkType.ChildLinkedSpan); + foreach (ISpan linkedSpan in parentLinks) + { + linkedSpan.AddLink(childLink); + span.AddLink(Link.FromSpanContext(linkedSpan.Context, LinkType.ParentLinkedSpan)); + } + } + } + + private static bool MakeSamplingDecision( + ISpanContext parent, + bool hasRemoteParent, + string name, + ISampler sampler, + IEnumerable parentLinks, + ITraceId traceId, + ISpanId spanId, + ITraceParams activeTraceParams) + { + // If users set a specific sampler in the SpanBuilder, use it. + if (sampler != null) + { + return sampler.ShouldSample(parent, hasRemoteParent, traceId, spanId, name, parentLinks); + } + + // Use the default sampler if this is a root Span or this is an entry point Span (has remote + // parent). + if (hasRemoteParent || parent == null || !parent.IsValid) + { + return activeTraceParams + .Sampler + .ShouldSample(parent, hasRemoteParent, traceId, spanId, name, parentLinks); + } + + // Parent is always different than null because otherwise we use the default sampler. + return parent.TraceOptions.IsSampled || IsAnyParentLinkSampled(parentLinks); + } + + private ISpan StartSpanInternal( + ISpanContext parent, + bool hasRemoteParent, + string name, + ISampler sampler, + IEnumerable parentLinks, + bool recordEvents, + Timer timestampConverter) + { + ITraceParams activeTraceParams = this.Options.TraceConfig.ActiveTraceParams; + IRandomGenerator random = this.Options.RandomHandler; + ITraceId traceId; + ISpanId spanId = SpanId.GenerateRandomId(random); + ISpanId parentSpanId = null; + TraceOptionsBuilder traceOptionsBuilder; + if (parent == null || !parent.IsValid) + { + // New root span. + traceId = TraceId.GenerateRandomId(random); + traceOptionsBuilder = TraceOptions.Builder(); + + // This is a root span so no remote or local parent. + // hasRemoteParent = null; + hasRemoteParent = false; + } + else + { + // New child span. + traceId = parent.TraceId; + parentSpanId = parent.SpanId; + traceOptionsBuilder = TraceOptions.Builder(parent.TraceOptions); + } + + traceOptionsBuilder.SetIsSampled( + MakeSamplingDecision( + parent, + hasRemoteParent, + name, + sampler, + parentLinks, + traceId, + spanId, + activeTraceParams)); + TraceOptions traceOptions = traceOptionsBuilder.Build(); + SpanOptions spanOptions = SpanOptions.None; + + if (traceOptions.IsSampled || recordEvents) + { + spanOptions = SpanOptions.RecordEvents; + } + + ISpan span = Span.StartSpan( + SpanContext.Create(traceId, spanId, traceOptions, parent?.Tracestate ?? Tracestate.Empty), + spanOptions, + name, + parentSpanId, + hasRemoteParent, + activeTraceParams, + this.Options.StartEndHandler, + timestampConverter); + LinkSpans(span, parentLinks); + span.Kind = this.Kind; + return span; + } + } +} diff --git a/src/OpenCensus/Trace/SpanBuilderBase.cs b/src/OpenCensus/Trace/SpanBuilderBase.cs new file mode 100644 index 000000000..c696a761b --- /dev/null +++ b/src/OpenCensus/Trace/SpanBuilderBase.cs @@ -0,0 +1,54 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System.Collections.Generic; + using OpenCensus.Common; + + public abstract class SpanBuilderBase : ISpanBuilder + { + private SpanBuilderBase() + { + } + + protected SpanKind Kind { get; private set; } + + protected SpanBuilderBase(SpanKind kind) + { + this.Kind = kind; + } + + public abstract ISpanBuilder SetSampler(ISampler sampler); + + public abstract ISpanBuilder SetParentLinks(IEnumerable parentLinks); + + public abstract ISpanBuilder SetRecordEvents(bool recordEvents); + + public abstract ISpan StartSpan(); + + public IScope StartScopedSpan() + { + return CurrentSpanUtils.WithSpan(this.StartSpan(), true); + } + + public IScope StartScopedSpan(out ISpan currentSpan) + { + currentSpan = this.StartSpan(); + return CurrentSpanUtils.WithSpan(currentSpan, true); + } + } +} diff --git a/src/OpenCensus/Trace/SpanBuilderOptions.cs b/src/OpenCensus/Trace/SpanBuilderOptions.cs new file mode 100644 index 000000000..41c36d312 --- /dev/null +++ b/src/OpenCensus/Trace/SpanBuilderOptions.cs @@ -0,0 +1,36 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Trace.Config; + + internal class SpanBuilderOptions + { + internal SpanBuilderOptions(IRandomGenerator randomGenerator, IStartEndHandler startEndHandler, ITraceConfig traceConfig) + { + this.RandomHandler = randomGenerator; + this.StartEndHandler = startEndHandler; + this.TraceConfig = traceConfig; + } + + internal IRandomGenerator RandomHandler { get; } + + internal IStartEndHandler StartEndHandler { get; } + + internal ITraceConfig TraceConfig { get; } + } +} diff --git a/src/OpenCensus/Trace/TraceComponent.cs b/src/OpenCensus/Trace/TraceComponent.cs new file mode 100644 index 000000000..b4bbc148d --- /dev/null +++ b/src/OpenCensus/Trace/TraceComponent.cs @@ -0,0 +1,79 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Internal; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Internal; + using OpenCensus.Trace.Propagation; + + /// + /// Trace component holds all the extensibility points required for distributed tracing. + /// + public sealed class TraceComponent : ITraceComponent + { + /// + /// Initializes a new instance of the class. + /// + public TraceComponent() + : this(new RandomGenerator(), new SimpleEventQueue()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Random numbers generator. + /// Event queue to use before the exporter. + public TraceComponent(IRandomGenerator randomHandler, IEventQueue eventQueue) + { + this.TraceConfig = new Config.TraceConfig(); + + // TODO(bdrutu): Add a config/argument for supportInProcessStores. + if (eventQueue is SimpleEventQueue) + { + this.ExportComponent = Export.ExportComponent.CreateWithoutInProcessStores(eventQueue); + } + else + { + this.ExportComponent = Export.ExportComponent.CreateWithInProcessStores(eventQueue); + } + + this.PropagationComponent = new DefaultPropagationComponent(); + IStartEndHandler startEndHandler = + new StartEndHandler( + this.ExportComponent.SpanExporter, + this.ExportComponent.RunningSpanStore, + this.ExportComponent.SampledSpanStore, + eventQueue); + this.Tracer = new Tracer(randomHandler, startEndHandler, this.TraceConfig); + } + + /// + public ITracer Tracer { get; } + + /// + public IPropagationComponent PropagationComponent { get; } + + /// + public IExportComponent ExportComponent { get; } + + /// + public ITraceConfig TraceConfig { get; } + } +} diff --git a/src/OpenCensus/Trace/TraceEvents.cs b/src/OpenCensus/Trace/TraceEvents.cs new file mode 100644 index 000000000..88b042048 --- /dev/null +++ b/src/OpenCensus/Trace/TraceEvents.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Utils; + + internal class TraceEvents + { + private readonly EvictingQueue events; + private int totalRecordedEvents = 0; + + public TraceEvents(int maxNumEvents) + { + this.events = new EvictingQueue(maxNumEvents); + } + + public EvictingQueue Events + { + get + { + return this.events; + } + } + + public int NumberOfDroppedEvents + { + get { return this.totalRecordedEvents - this.events.Count; } + } + + internal void AddEvent(T @event) + { + this.totalRecordedEvents++; + this.events.Add(@event); + } + } +} diff --git a/src/OpenCensus/Trace/Tracer.cs b/src/OpenCensus/Trace/Tracer.cs new file mode 100644 index 000000000..710ffa913 --- /dev/null +++ b/src/OpenCensus/Trace/Tracer.cs @@ -0,0 +1,40 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Trace.Config; + + public sealed class Tracer : TracerBase + { + private readonly SpanBuilderOptions spanBuilderOptions; + + public Tracer(IRandomGenerator randomGenerator, IStartEndHandler startEndHandler, ITraceConfig traceConfig) + { + this.spanBuilderOptions = new SpanBuilderOptions(randomGenerator, startEndHandler, traceConfig); + } + + public override ISpanBuilder SpanBuilderWithExplicitParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpan parent = null) + { + return Trace.SpanBuilder.CreateWithParent(spanName, spanKind, parent, this.spanBuilderOptions); + } + + public override ISpanBuilder SpanBuilderWithRemoteParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpanContext remoteParentSpanContext = null) + { + return Trace.SpanBuilder.CreateWithRemoteParent(spanName, spanKind, remoteParentSpanContext, this.spanBuilderOptions); + } + } +} diff --git a/src/OpenCensus/Trace/TracerBase.cs b/src/OpenCensus/Trace/TracerBase.cs new file mode 100644 index 000000000..0bcb48351 --- /dev/null +++ b/src/OpenCensus/Trace/TracerBase.cs @@ -0,0 +1,65 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using System; + using OpenCensus.Common; + using OpenCensus.Trace.Internal; + + public abstract class TracerBase : ITracer + { + private static readonly NoopTracer NoopTracerInstance = new NoopTracer(); + + public ISpan CurrentSpan + { + get + { + ISpan currentSpan = CurrentSpanUtils.CurrentSpan; + return currentSpan ?? BlankSpan.Instance; + } + } + + internal static NoopTracer NoopTracer + { + get + { + return NoopTracerInstance; + } + } + + public IScope WithSpan(ISpan span) + { + if (span == null) + { + throw new ArgumentNullException(nameof(span)); + } + + return CurrentSpanUtils.WithSpan(span, false); + } + + // public final Runnable withSpan(Span span, Runnable runnable) + // public final Callable withSpan(Span span, final Callable callable) + public ISpanBuilder SpanBuilder(string spanName, SpanKind spanKind = SpanKind.Unspecified) + { + return this.SpanBuilderWithExplicitParent(spanName, spanKind, CurrentSpanUtils.CurrentSpan); + } + + public abstract ISpanBuilder SpanBuilderWithExplicitParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpan parent = null); + + public abstract ISpanBuilder SpanBuilderWithRemoteParent(string spanName, SpanKind spanKind = SpanKind.Unspecified, ISpanContext remoteParentSpanContext = null); + } +} diff --git a/src/OpenCensus/Trace/Tracing.cs b/src/OpenCensus/Trace/Tracing.cs new file mode 100644 index 000000000..50b91691a --- /dev/null +++ b/src/OpenCensus/Trace/Tracing.cs @@ -0,0 +1,66 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace +{ + using OpenCensus.Internal; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Internal; + using OpenCensus.Trace.Propagation; + + /// + /// Helper class that provides easy to use static constructor of the default tracer component. + /// + public sealed class Tracing + { + private static Tracing tracing = new Tracing(true); + + private ITraceComponent traceComponent = null; + + internal Tracing(bool enabled) + { + if (enabled) + { + this.traceComponent = new TraceComponent(new RandomGenerator(), new SimpleEventQueue()); + } + else + { + this.traceComponent = new NoopTraceComponent(); + } + } + + /// + /// Gets the tracer to record spans. + /// + public static ITracer Tracer => tracing.traceComponent.Tracer; + + /// + /// Gets the propagation component that defines how to extract and inject span context from the wire protocols. + /// + public static IPropagationComponent PropagationComponent => tracing.traceComponent.PropagationComponent; + + /// + /// Gets the export component to upload spans to. + /// + public static IExportComponent ExportComponent => tracing.traceComponent.ExportComponent; + + /// + /// Gets the tracer configuration. + /// + public static ITraceConfig TraceConfig => tracing.traceComponent.TraceConfig; + } +} diff --git a/src/OpenCensus/Utils/Arrays.cs b/src/OpenCensus/Utils/Arrays.cs new file mode 100644 index 000000000..c6c2cf782 --- /dev/null +++ b/src/OpenCensus/Utils/Arrays.cs @@ -0,0 +1,145 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System; + using System.Text; + + internal static class Arrays + { + private static readonly uint[] ByteToHexLookupTable = CreateLookupTable(); + + public static bool Equals(byte[] array1, byte[] array2) + { + if (array1 == array2) + { + return true; + } + + if (array1 == null || array2 == null) + { + return false; + } + + if (array2.Length != array1.Length) + { + return false; + } + + for (int i = 0; i < array1.Length; i++) + { + if (array1[i] != array2[i]) + { + return false; + } + } + + return true; + } + + internal static int GetHashCode(byte[] array) + { + if (array == null) + { + return 0; + } + + int result = 1; + foreach (byte b in array) + { + result = (31 * result) + b; + } + + return result; + } + + internal static int HexCharToInt(char c) + { + if ((c >= '0') && (c <= '9')) + { + return c - '0'; + } + + if ((c >= 'a') && (c <= 'f')) + { + return c - 'a' + 10; + } + + if ((c >= 'A') && (c <= 'F')) + { + return c - 'A' + 10; + } + + throw new ArgumentOutOfRangeException("Invalid character: " + c); + } + + // https://stackoverflow.com/a/24343727 + internal static uint[] CreateLookupTable() + { + uint[] table = new uint[256]; + for (int i = 0; i < 256; i++) + { + string s = i.ToString("x2"); + table[i] = (uint)s[0]; + table[i] += (uint)s[1] << 16; + } + + return table; + } + + // https://stackoverflow.com/a/24343727 + internal static char[] ByteToHexCharArray(byte b) + { + char[] result = new char[2]; + + result[0] = (char)ByteToHexLookupTable[b]; + result[1] = (char)(ByteToHexLookupTable[b] >> 16); + + return result; + } + + internal static byte[] StringToByteArray(string src, int start = 0, int len = -1) + { + if (len == -1) + { + len = src.Length; + } + + int size = len / 2; + byte[] bytes = new byte[size]; + for (int i = 0, j = start; i < size; i++) + { + int high = HexCharToInt(src[j++]); + int low = HexCharToInt(src[j++]); + bytes[i] = (byte)(high << 4 | low); + } + + return bytes; + } + + internal static string ByteArrayToString(byte[] bytes) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.Length; i++) + { + sb.Append(ByteToHexCharArray(bytes[i])); + } + + return sb.ToString(); + } + } +} diff --git a/src/OpenCensus/Utils/AttributesWithCapacity.cs b/src/OpenCensus/Utils/AttributesWithCapacity.cs new file mode 100644 index 000000000..91ff08fce --- /dev/null +++ b/src/OpenCensus/Utils/AttributesWithCapacity.cs @@ -0,0 +1,193 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Linq; + using OpenCensus.Trace; + + internal class AttributesWithCapacity : IDictionary + { + private readonly OrderedDictionary @delegate = new OrderedDictionary(); + private readonly int capacity; + private int totalRecordedAttributes; + + public AttributesWithCapacity(int capacity) + { + this.capacity = capacity; + } + + public int NumberOfDroppedAttributes + { + get + { + return this.totalRecordedAttributes - this.Count; + } + } + + public ICollection Keys + { + get + { + return (ICollection)this.@delegate.Keys; + } + } + + public ICollection Values + { + get + { + return (ICollection)this.@delegate.Values; + } + } + + public int Count + { + get + { + return this.@delegate.Count; + } + } + + public bool IsReadOnly + { + get + { + return this.@delegate.IsReadOnly; + } + } + + public IAttributeValue this[string key] + { + get + { + return (IAttributeValue)this.@delegate[key]; + } + + set + { + this.@delegate[key] = value; + } + } + + public void PutAttribute(string key, IAttributeValue value) + { + this.totalRecordedAttributes += 1; + this[key] = value; + if (this.Count > this.capacity) + { + this.@delegate.RemoveAt(0); + } + } + + // Users must call this method instead of putAll to keep count of the total number of entries + // inserted. + public void PutAttributes(IDictionary attributes) + { + foreach (var kvp in attributes) + { + this.PutAttribute(kvp.Key, kvp.Value); + } + } + + public void Add(string key, IAttributeValue value) + { + this.@delegate.Add(key, value); + } + + public bool ContainsKey(string key) + { + return this.@delegate.Contains(key); + } + + public bool Remove(string key) + { + if (this.@delegate.Contains(key)) + { + this.@delegate.Remove(key); + return true; + } + else + { + return false; + } + } + + public bool TryGetValue(string key, out IAttributeValue value) + { + value = null; + if (this.ContainsKey(key)) + { + value = (IAttributeValue)this.@delegate[key]; + return true; + } + + return false; + } + + public void Add(KeyValuePair item) + { + this.@delegate.Add(item.Key, item.Value); + } + + public void Clear() + { + this.@delegate.Clear(); + } + + public bool Contains(KeyValuePair item) + { + var result = this.TryGetValue(item.Key, out IAttributeValue value); + if (result) + { + return value.Equals(item.Value); + } + + return false; + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + DictionaryEntry[] entries = new DictionaryEntry[this.@delegate.Count]; + this.@delegate.CopyTo(entries, 0); + + for (int i = 0; i < entries.Length; i++) + { + array[i + arrayIndex] = new KeyValuePair((string)entries[i].Key, (IAttributeValue)entries[i].Value); + } + } + + public bool Remove(KeyValuePair item) + { + return this.Remove(item.Key); + } + + public IEnumerator> GetEnumerator() + { + var array = new KeyValuePair[this.@delegate.Count]; + this.CopyTo(array, 0); + return array.ToList().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.@delegate.GetEnumerator(); + } + } +} diff --git a/src/OpenCensus/Utils/Collections.cs b/src/OpenCensus/Utils/Collections.cs new file mode 100644 index 000000000..5889227b9 --- /dev/null +++ b/src/OpenCensus/Utils/Collections.cs @@ -0,0 +1,71 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System.Collections.Generic; + using System.Linq; + using System.Text; + + internal static class Collections + { + public static string ToString(IDictionary dict) + { + if (dict == null) + { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + foreach (var kvp in dict) + { + sb.Append(kvp.Key.ToString()); + sb.Append("="); + sb.Append(kvp.Value.ToString()); + sb.Append(" "); + } + + return sb.ToString(); + } + + public static string ToString(IEnumerable list) + { + if (list == null) + { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + foreach (var val in list) + { + if (val != null) + { + sb.Append(val.ToString()); + sb.Append(" "); + } + } + + return sb.ToString(); + } + + public static bool AreEquivalent(IEnumerable c1, IEnumerable c2) + { + var c1Dist = c1.Distinct(); + var c2Dist = c2.Distinct(); + return c1.Count() == c2.Count() && c1Dist.Count() == c2Dist.Count() && c1Dist.Intersect(c2Dist).Count() == c1Dist.Count(); + } + } +} diff --git a/src/OpenCensus/Utils/ConcurrentIntrusiveList.cs b/src/OpenCensus/Utils/ConcurrentIntrusiveList.cs new file mode 100644 index 000000000..2cf376c1a --- /dev/null +++ b/src/OpenCensus/Utils/ConcurrentIntrusiveList.cs @@ -0,0 +1,115 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System; + using System.Collections.Generic; + + internal sealed class ConcurrentIntrusiveList where T : IElement + { + private readonly object lck = new object(); + private int size = 0; + private T head = default(T); + + public ConcurrentIntrusiveList() + { + } + + public int Count + { + get + { + return this.size; + } + } + + public void AddElement(T element) + { + lock (this.lck) + { + if (element.Next != null || element.Previous != null || element.Equals(this.head)) + { + throw new ArgumentOutOfRangeException("Element already in a list"); + } + + this.size++; + if (this.head == null) + { + this.head = element; + } + else + { + this.head.Previous = element; + element.Next = this.head; + this.head = element; + } + } + } + + public void RemoveElement(T element) + { + lock (this.lck) + { + if (element.Next == null && element.Previous == null && !element.Equals(this.head)) + { + throw new ArgumentOutOfRangeException("Element not in the list"); + } + + this.size--; + if (element.Previous == null) + { + // This is the first element + this.head = element.Next; + if (this.head != null) + { + // If more than one element in the list. + this.head.Previous = default(T); + element.Next = default(T); + } + } + else if (element.Next == null) + { + // This is the last element, and there is at least another element because + // element.getPrev() != null. + element.Previous.Next = default(T); + element.Previous = default(T); + } + else + { + element.Previous.Next = element.Next; + element.Next.Previous = element.Previous; + element.Next = default(T); + element.Previous = default(T); + } + } + } + + public IReadOnlyList Copy() + { + lock (this.lck) + { + List all = new List(this.size); + for (T e = this.head; e != null; e = e.Next) + { + all.Add(e); + } + + return all; + } + } + } +} diff --git a/src/OpenCensus/Utils/DefaultEventQueue.cs b/src/OpenCensus/Utils/DefaultEventQueue.cs new file mode 100644 index 000000000..99df1b014 --- /dev/null +++ b/src/OpenCensus/Utils/DefaultEventQueue.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System; + using OpenCensus.Internal; + + internal class DefaultEventQueue : IEventQueue + { + public void Enqueue(IEventQueueEntry entry) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/OpenCensus/Utils/DoubleUtil.cs b/src/OpenCensus/Utils/DoubleUtil.cs new file mode 100644 index 000000000..e12000d11 --- /dev/null +++ b/src/OpenCensus/Utils/DoubleUtil.cs @@ -0,0 +1,56 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System; + + internal static class DoubleUtil + { + public static long ToInt64(double arg) + { + if (double.IsPositiveInfinity(arg)) + { + return 0x7ff0000000000000L; + } + + if (double.IsNegativeInfinity(arg)) + { + unchecked + { + return (long)0xfff0000000000000L; + } + } + + if (double.IsNaN(arg)) + { + return 0x7ff8000000000000L; + } + + if (arg == double.MaxValue) + { + return long.MaxValue; + } + + if (arg == double.MinValue) + { + return long.MinValue; + } + + return Convert.ToInt64(arg); + } + } +} diff --git a/src/OpenCensus/Utils/EvictingQueue.cs b/src/OpenCensus/Utils/EvictingQueue.cs new file mode 100644 index 000000000..335b8a755 --- /dev/null +++ b/src/OpenCensus/Utils/EvictingQueue.cs @@ -0,0 +1,113 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + using System; + using System.Collections; + using System.Collections.Generic; + + internal class EvictingQueue : IEnumerable, IEnumerable + { + private readonly Queue @delegate; + private readonly int maxSize; + + public EvictingQueue(int maxSize) + { + if (maxSize < 0) + { + throw new ArgumentOutOfRangeException("maxSize must be >= 0"); + } + + this.maxSize = maxSize; + this.@delegate = new Queue(maxSize); + } + + public int Count + { + get + { + return this.@delegate.Count; + } + } + + public int RemainingCapacity() + { + return this.maxSize - this.@delegate.Count; + } + + public bool Offer(T e) + { + return this.Add(e); + } + + public bool Add(T e) + { + if (e == null) + { + throw new ArgumentNullException(); + } + + if (this.maxSize == 0) + { + return true; + } + + if (this.@delegate.Count == this.maxSize) + { + this.@delegate.Dequeue(); + } + + this.@delegate.Enqueue(e); + return true; + } + + public bool AddAll(ICollection collection) + { + foreach (var e in collection) + { + this.Add(e); + } + + return true; + } + + public bool Contains(T e) + { + if (e == null) + { + throw new ArgumentNullException(); + } + + return this.@delegate.Contains(e); + } + + public T[] ToArray() + { + return this.@delegate.ToArray(); + } + + public IEnumerator GetEnumerator() + { + return this.@delegate.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.@delegate.GetEnumerator(); + } + } +} diff --git a/src/OpenCensus/Utils/StringUtil.cs b/src/OpenCensus/Utils/StringUtil.cs new file mode 100644 index 000000000..16e363a73 --- /dev/null +++ b/src/OpenCensus/Utils/StringUtil.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Utils +{ + internal static class StringUtil + { + public static bool IsPrintableString(string str) + { + for (int i = 0; i < str.Length; i++) + { + if (!IsPrintableChar(str[i])) + { + return false; + } + } + + return true; + } + + private static bool IsPrintableChar(char ch) + { + return ch >= ' ' && ch <= '~'; + } + } +} diff --git a/src/Samples/Program.cs b/src/Samples/Program.cs new file mode 100644 index 000000000..c18e220d4 --- /dev/null +++ b/src/Samples/Program.cs @@ -0,0 +1,71 @@ +namespace Samples +{ + using CommandLine; + using System; + + [Verb("stackdriver", HelpText = "Specify the options required to test Stackdriver exporter", Hidden = false)] + class StackdriverOptions + { + [Option('p', "projectId", HelpText = "Please specify the projectId of your GCP project", Required = true)] + public string ProjectId { get; set; } + } + + [Verb("zipkin", HelpText = "Specify the options required to test Zipkin exporter")] + class ZipkinOptions + { + [Option('u', "uri", HelpText = "Please specify the uri of Zipkin backend", Required = true)] + public string Uri { get; set; } + } + + [Verb("appInsights", HelpText = "Specify the options required to test ApplicationInsights")] + class ApplicationInsightsOptions + { + } + + [Verb("prometheus", HelpText = "Specify the options required to test Prometheus")] + class PrometheusOptions + { + } + + [Verb("httpclient", HelpText = "Specify the options required to test HttpClient")] + class HttpClientOptions + { + } + + [Verb("redis", HelpText = "Specify the options required to test Redis with Zipkin")] + class RedisOptions + { + [Option('u', "uri", HelpText = "Please specify the uri of Zipkin backend", Required = true)] + public string Uri { get; set; } + } + + /// + /// Main samples entry point. + /// + public class Program + { + /// + /// Main method - invoke this using command line. + /// For example: + /// + /// Samples.dll zipkin http://localhost:9411/api/v2/spans + /// Sample.dll appInsights + /// Sample.dll prometheus + /// + /// Arguments from command line. + public static void Main(string[] args) + { + Parser.Default.ParseArguments(args) + .MapResult( + (ZipkinOptions options) => TestZipkin.Run(options.Uri), + (ApplicationInsightsOptions options) => TestApplicationInsights.Run(), + (PrometheusOptions options) => TestPrometheus.Run(), + (HttpClientOptions options) => TestHttpClient.Run(), + (RedisOptions options) => TestRedis.Run(options.Uri), + (StackdriverOptions options) => TestStackdriver.Run(options.ProjectId), + errs => 1); + + Console.ReadLine(); + } + } +} diff --git a/src/Samples/Samples.csproj b/src/Samples/Samples.csproj new file mode 100644 index 000000000..8dc4268c2 --- /dev/null +++ b/src/Samples/Samples.csproj @@ -0,0 +1,23 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + diff --git a/src/Samples/TestApplicationInsights.cs b/src/Samples/TestApplicationInsights.cs new file mode 100644 index 000000000..63dd77a0a --- /dev/null +++ b/src/Samples/TestApplicationInsights.cs @@ -0,0 +1,73 @@ +namespace Samples +{ + using System; + using System.Collections.Generic; + using System.Threading; + using Microsoft.ApplicationInsights.Extensibility; + using OpenCensus.Exporter.ApplicationInsights; + using OpenCensus.Stats; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using OpenCensus.Trace; + using OpenCensus.Trace.Sampler; + + internal class TestApplicationInsights + { + private static ITracer tracer = Tracing.Tracer; + private static ITagger tagger = Tags.Tagger; + + private static IStatsRecorder statsRecorder = Stats.StatsRecorder; + private static readonly IMeasureLong VideoSize = MeasureLong.Create("my.org/measure/video_size", "size of processed videos", "By"); + private static readonly ITagKey FrontendKey = TagKey.Create("my.org/keys/frontend"); + + private static long MiB = 1 << 20; + + private static readonly IViewName VideoSizeViewName = ViewName.Create("my.org/views/video_size"); + + private static readonly IView VideoSizeView = View.Create( + VideoSizeViewName, + "processed video size over time", + VideoSize, + Distribution.Create(BucketBoundaries.Create(new List() { 0.0, 16.0 * MiB, 256.0 * MiB })), + new List() { FrontendKey }); + + internal static object Run() + { + TelemetryConfiguration.Active.InstrumentationKey = "instrumentation-key"; + var exporter = new ApplicationInsightsExporter(Tracing.ExportComponent, Stats.ViewManager, TelemetryConfiguration.Active); + exporter.Start(); + + ITagContextBuilder tagContextBuilder = tagger.CurrentBuilder.Put(FrontendKey, TagValue.Create("mobile-ios9.3.5")); + + var spanBuilder = tracer + .SpanBuilder("incoming request") + .SetRecordEvents(true) + .SetSampler(Samplers.AlwaysSample); + + Stats.ViewManager.RegisterView(VideoSizeView); + + using (var scopedTags = tagContextBuilder.BuildScoped()) + { + using (var scopedSpan = spanBuilder.StartScopedSpan()) + { + tracer.CurrentSpan.AddAnnotation("Start processing video."); + Thread.Sleep(TimeSpan.FromMilliseconds(10)); + statsRecorder.NewMeasureMap().Put(VideoSize, 25 * MiB).Record(); + tracer.CurrentSpan.AddAnnotation("Finished processing video."); + } + } + + Thread.Sleep(TimeSpan.FromMilliseconds(5100)); + + var viewData = Stats.ViewManager.GetView(VideoSizeViewName); + + Console.WriteLine(viewData); + + Console.WriteLine("Done... wait for events to arrive to backend!"); + Console.ReadLine(); + + return null; + } + } +} diff --git a/src/Samples/TestHttpClient.cs b/src/Samples/TestHttpClient.cs new file mode 100644 index 000000000..9161f0566 --- /dev/null +++ b/src/Samples/TestHttpClient.cs @@ -0,0 +1,45 @@ +namespace Samples +{ + using System; + using System.Net.Http; + using OpenCensus.Collector.Dependencies; + using OpenCensus.Exporter.Zipkin; + using OpenCensus.Trace; + using OpenCensus.Trace.Propagation; + using OpenCensus.Trace.Sampler; + + internal class TestHttpClient + { + private static ITracer tracer = Tracing.Tracer; + + internal static object Run() + { + Console.WriteLine("Hello World!"); + + var collector = new DependenciesCollector(new DependenciesCollectorOptions(), tracer, Samplers.AlwaysSample, PropagationComponentBase.NoopPropagationComponent); + + var exporter = new ZipkinTraceExporter( + new ZipkinTraceExporterOptions() + { + Endpoint = new Uri("https://zipkin.azurewebsites.net/api/v2/spans"), + ServiceName = typeof(Program).Assembly.GetName().Name, + }, + Tracing.ExportComponent); + exporter.Start(); + + var scope = tracer.SpanBuilder("incoming request").SetSampler(Samplers.AlwaysSample).StartScopedSpan(); + //Thread.Sleep(TimeSpan.FromSeconds(1)); + + HttpClient client = new HttpClient(); + var t = client.GetStringAsync("http://bing.com"); + + t.Wait(); + + scope.Dispose(); + + Console.ReadLine(); + + return null; + } + } +} diff --git a/src/Samples/TestPrometheus.cs b/src/Samples/TestPrometheus.cs new file mode 100644 index 000000000..202bcd051 --- /dev/null +++ b/src/Samples/TestPrometheus.cs @@ -0,0 +1,79 @@ +namespace Samples +{ + using OpenCensus.Exporter.Prometheus; + using OpenCensus.Stats; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using OpenCensus.Trace; + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + internal class TestPrometheus + { + private static ITracer tracer = Tracing.Tracer; + private static ITagger tagger = Tags.Tagger; + + private static IStatsRecorder statsRecorder = Stats.StatsRecorder; + private static readonly IMeasureLong VideoSize = MeasureLong.Create("my.org/measure/video_size", "size of processed videos", "By"); + private static readonly ITagKey FrontendKey = TagKey.Create("my.org/keys/frontend"); + + private static long MiB = 1 << 20; + + private static readonly IViewName VideoSizeViewName = ViewName.Create("my.org/views/video_size"); + + private static readonly IView VideoSizeView = View.Create( + VideoSizeViewName, + "processed video size over time", + VideoSize, + Distribution.Create(BucketBoundaries.Create(new List() { 0.0, 16.0 * MiB, 256.0 * MiB })), + new List() { FrontendKey }); + + internal static object Run() + { + var exporter = new PrometheusExporter( + new PrometheusExporterOptions() + { + Url = new Uri("http://localhost:9184/metrics/") + }, + Stats.ViewManager); + + exporter.Start(); + + try + { + ITagContextBuilder tagContextBuilder = tagger.CurrentBuilder.Put(FrontendKey, TagValue.Create("mobile-ios9.3.5")); + + Stats.ViewManager.RegisterView(VideoSizeView); + + var t = new Task(() => + { + Random r = new Random(); + byte[] values = new byte[1]; + + while (true) + { + using (var scopedTags = tagContextBuilder.BuildScoped()) + { + r.NextBytes(values); + statsRecorder.NewMeasureMap().Put(VideoSize, values[0] * MiB).Record(); + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + } + }); + t.Start(); + + Console.WriteLine("Look at metrics in Prometetheus console!"); + Console.ReadLine(); + } + finally + { + exporter.Stop(); + } + + return null; + } + } +} diff --git a/src/Samples/TestRedis.cs b/src/Samples/TestRedis.cs new file mode 100644 index 000000000..1c178af9c --- /dev/null +++ b/src/Samples/TestRedis.cs @@ -0,0 +1,104 @@ +namespace Samples +{ + using System; + using System.Collections.Generic; + using System.Threading; + using OpenCensus.Collector.StackExchangeRedis; + using OpenCensus.Exporter.Zipkin; + using OpenCensus.Trace; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Sampler; + using StackExchange.Redis; + + internal class TestRedis + { + internal static object Run(string zipkinUri) + { + // 1. Configure exporter to export traces to Zipkin + var exporter = new ZipkinTraceExporter( + new ZipkinTraceExporterOptions() + { + Endpoint = new Uri(zipkinUri), + ServiceName = "tracing-to-zipkin-service", + }, + Tracing.ExportComponent); + exporter.Start(); + + // 2. Configure 100% sample rate for the purposes of the demo + ITraceConfig traceConfig = Tracing.TraceConfig; + ITraceParams currentConfig = traceConfig.ActiveTraceParams; + var newConfig = currentConfig.ToBuilder() + .SetSampler(Samplers.AlwaysSample) + .Build(); + traceConfig.UpdateActiveTraceParams(newConfig); + + // 3. Tracer is global singleton. You can register it via dependency injection if it exists + // but if not - you can use it as follows: + var tracer = Tracing.Tracer; + + var collector = new StackExchangeRedisCallsCollector(null, tracer, null, Tracing.ExportComponent); + + // connect to the server + ConnectionMultiplexer connection = ConnectionMultiplexer.Connect("localhost:6379"); + connection.RegisterProfiler(collector.GetProfilerSessionsFactory()); + + // select a database (by default, DB = 0) + IDatabase db = connection.GetDatabase(); + + + // 4. Create a scoped span. It will end automatically when using statement ends + using (var scope = tracer.SpanBuilder("Main").StartScopedSpan()) + { + Console.WriteLine("About to do a busy work"); + for (int i = 0; i < 10; i++) + { + DoWork(db, i); + } + } + + // 5. Gracefully shutdown the exporter so it'll flush queued traces to Zipkin. + Tracing.ExportComponent.SpanExporter.Dispose(); + + return null; + } + + private static void DoWork(IDatabase db, int i) + { + // 6. Get the global singleton Tracer object + ITracer tracer = Tracing.Tracer; + + // 7. Start another span. If another span was already started, it'll use that span as the parent span. + // In this example, the main method already started a span, so that'll be the parent span, and this will be + // a child span. + using (OpenCensus.Common.IScope scope = tracer.SpanBuilder("DoWork").StartScopedSpan()) + { + // Simulate some work. + ISpan span = tracer.CurrentSpan; + + try + { + db.StringSet("key", "value " + DateTime.Now.ToLongDateString()); + + Console.WriteLine("Doing busy work"); + Thread.Sleep(1000); + + // run a command, in this case a GET + RedisValue myVal = db.StringGet("key"); + + Console.WriteLine(myVal); + + } + catch (ArgumentOutOfRangeException e) + { + // 6. Set status upon error + span.Status = Status.Internal.WithDescription(e.ToString()); + } + + // 7. Annotate our span to capture metadata about our operation + var attributes = new Dictionary(); + attributes.Add("use", AttributeValue.StringAttributeValue("demo")); + span.AddAnnotation("Invoking DoWork", attributes); + } + } + } +} diff --git a/src/Samples/TestStackdriver.cs b/src/Samples/TestStackdriver.cs new file mode 100644 index 000000000..4a88c92d2 --- /dev/null +++ b/src/Samples/TestStackdriver.cs @@ -0,0 +1,76 @@ +namespace Samples +{ + using System; + using System.Collections.Generic; + using System.Threading; + using OpenCensus.Exporter.Stackdriver; + using OpenCensus.Stats; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using OpenCensus.Trace; + using OpenCensus.Trace.Sampler; + + internal class TestStackdriver + { + private static ITracer tracer = Tracing.Tracer; + private static ITagger tagger = Tags.Tagger; + + private static IStatsRecorder statsRecorder = Stats.StatsRecorder; + private static readonly IMeasureDouble VideoSize = MeasureDouble.Create("my_org/measure/video_size", "size of processed videos", "MiB"); + private static readonly ITagKey FrontendKey = TagKey.Create("my_org/keys/frontend"); + + private static long MiB = 1 << 20; + + private static readonly IViewName VideoSizeViewName = ViewName.Create("my_org/views/video_size"); + + private static readonly IView VideoSizeView = View.Create( + name: VideoSizeViewName, + description: "processed video size over time", + measure: VideoSize, + aggregation: Sum.Create(), + columns: new List() { FrontendKey }); + + internal static object Run(string projectId) + { + var exporter = new StackdriverExporter( + projectId, + Tracing.ExportComponent, + Stats.ViewManager); + exporter.Start(); + + ITagContextBuilder tagContextBuilder = tagger.CurrentBuilder.Put(FrontendKey, TagValue.Create("mobile-ios9.3.5")); + + var spanBuilder = tracer + .SpanBuilder("incoming request") + .SetRecordEvents(true) + .SetSampler(Samplers.AlwaysSample); + + Stats.ViewManager.RegisterView(VideoSizeView); + + using (var scopedTags = tagContextBuilder.BuildScoped()) + { + using (var scopedSpan = spanBuilder.StartScopedSpan()) + { + tracer.CurrentSpan.AddAnnotation("Processing video."); + Thread.Sleep(TimeSpan.FromMilliseconds(10)); + + statsRecorder.NewMeasureMap() + .Put(VideoSize, 25 * MiB) + .Record(); + } + } + + Thread.Sleep(TimeSpan.FromMilliseconds(5100)); + + var viewData = Stats.ViewManager.GetView(VideoSizeViewName); + + Console.WriteLine(viewData); + + Console.WriteLine("Done... wait for events to arrive to backend!"); + Console.ReadLine(); + + return null; + } + } +} diff --git a/src/Samples/TestZipkin.cs b/src/Samples/TestZipkin.cs new file mode 100644 index 000000000..02db01177 --- /dev/null +++ b/src/Samples/TestZipkin.cs @@ -0,0 +1,84 @@ +namespace Samples +{ + using System; + using System.Collections.Generic; + using System.Threading; + using OpenCensus.Exporter.Zipkin; + using OpenCensus.Trace; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Sampler; + + internal class TestZipkin + { + internal static object Run(string zipkinUri) + { + // 1. Configure exporter to export traces to Zipkin + var exporter = new ZipkinTraceExporter( + new ZipkinTraceExporterOptions() + { + Endpoint = new Uri(zipkinUri), + ServiceName = "tracing-to-zipkin-service", + }, + Tracing.ExportComponent); + exporter.Start(); + + // 2. Configure 100% sample rate for the purposes of the demo + ITraceConfig traceConfig = Tracing.TraceConfig; + ITraceParams currentConfig = traceConfig.ActiveTraceParams; + var newConfig = currentConfig.ToBuilder() + .SetSampler(Samplers.AlwaysSample) + .Build(); + traceConfig.UpdateActiveTraceParams(newConfig); + + // 3. Tracer is global singleton. You can register it via dependency injection if it exists + // but if not - you can use it as follows: + var tracer = Tracing.Tracer; + + // 4. Create a scoped span. It will end automatically when using statement ends + using (var scope = tracer.SpanBuilder("Main").StartScopedSpan()) + { + Console.WriteLine("About to do a busy work"); + for (int i = 0; i < 10; i++) + { + DoWork(i); + } + } + + // 5. Gracefully shutdown the exporter so it'll flush queued traces to Zipkin. + Tracing.ExportComponent.SpanExporter.Dispose(); + + return null; + } + + private static void DoWork(int i) + { + // 6. Get the global singleton Tracer object + ITracer tracer = Tracing.Tracer; + + // 7. Start another span. If another span was already started, it'll use that span as the parent span. + // In this example, the main method already started a span, so that'll be the parent span, and this will be + // a child span. + using (OpenCensus.Common.IScope scope = tracer.SpanBuilder("DoWork").StartScopedSpan()) + { + // Simulate some work. + ISpan span = tracer.CurrentSpan; + + try + { + Console.WriteLine("Doing busy work"); + Thread.Sleep(1000); + } + catch (ArgumentOutOfRangeException e) + { + // 6. Set status upon error + span.Status = Status.Internal.WithDescription(e.ToString()); + } + + // 7. Annotate our span to capture metadata about our operation + var attributes = new Dictionary(); + attributes.Add("use", AttributeValue.StringAttributeValue("demo")); + span.AddAnnotation("Invoking DoWork", attributes); + } + } + } +} diff --git a/test/OpenCensus.Collector.AspNetCore.Tests/BasicTests.cs b/test/OpenCensus.Collector.AspNetCore.Tests/BasicTests.cs new file mode 100644 index 000000000..0e4800ada --- /dev/null +++ b/test/OpenCensus.Collector.AspNetCore.Tests/BasicTests.cs @@ -0,0 +1,154 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore.Tests +{ + using Xunit; + using Microsoft.AspNetCore.Mvc.Testing; + using TestApp.AspNetCore._2._0; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using OpenCensus.Trace; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using OpenCensus.Common; + using Moq; + using Microsoft.AspNetCore.TestHost; + using System; + using OpenCensus.Trace.Propagation; + using Microsoft.AspNetCore.Http; + using System.Collections.Generic; + + // See https://github.com/aspnet/Docs/tree/master/aspnetcore/test/integration-tests/samples/2.x/IntegrationTestsSample + public class BasicTests + : IClassFixture> + { + private readonly WebApplicationFactory factory; + + public BasicTests(WebApplicationFactory factory) + { + this.factory = factory; + + } + + [Fact] + public async Task SuccesfulTemplateControllerCallGeneratesASpan() + { + var startEndHandler = new Mock(); + var tracer = new Tracer(new RandomGenerator(), startEndHandler.Object, new TraceConfig()); + + void ConfigureTestServices(IServiceCollection services) => + services.AddSingleton(tracer); + + // Arrange + using (var client = this.factory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices(ConfigureTestServices)) + .CreateClient()) + { + + // Act + var response = await client.GetAsync("/api/values"); + + // Assert + response.EnsureSuccessStatusCode(); // Status Code 200-299 + + for (int i = 0; i < 10; i++) + { + if (startEndHandler.Invocations.Count == 2) + { + break; + } + + // We need to let End callback execute as it is executed AFTER response was returned. + // In unit tests environment there may be a lot of parallel unit tests executed, so + // giving some breezing room for the End callback to complete + await Task.Delay(TimeSpan.FromSeconds(1)); + } + } + + + Assert.Equal(2, startEndHandler.Invocations.Count); // begin and end was called + var spanData = ((Span)startEndHandler.Invocations[1].Arguments[0]).ToSpanData(); + + Assert.Equal(SpanKind.Server, spanData.Kind); + Assert.Equal(AttributeValue.StringAttributeValue("/api/values"), spanData.Attributes.AttributeMap["http.path"]); + } + + [Fact] + public async Task SuccesfulTemplateControllerCallUsesRemoteParentContext() + { + var startEndHandler = new Mock(); + var tracer = new Tracer(new RandomGenerator(), startEndHandler.Object, new TraceConfig()); + + var expectedTraceId = TraceId.GenerateRandomId(new RandomGenerator()); + var expectedSpanId = SpanId.GenerateRandomId(new RandomGenerator()); + + var tf = new Mock(); + tf.Setup(m => m.Extract(It.IsAny(), It.IsAny>>())).Returns(SpanContext.Create( + expectedTraceId, + expectedSpanId, + TraceOptions.Default, + Tracestate.Empty + )); + + var propagationComponent = new Mock(); + propagationComponent.SetupGet(m => m.TextFormat).Returns(tf.Object); + + + // Arrange + using (var client = this.factory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices((services) => + { + services.AddSingleton(tracer); + services.AddSingleton(propagationComponent.Object); + })) + .CreateClient()) + { + + // Act + var response = await client.GetAsync("/api/values/2"); + + // Assert + response.EnsureSuccessStatusCode(); // Status Code 200-299 + + for (int i = 0; i < 10; i++) + { + if (startEndHandler.Invocations.Count == 2) + { + break; + } + + // We need to let End callback execute as it is executed AFTER response was returned. + // In unit tests environment there may be a lot of parallel unit tests executed, so + // giving some breezing room for the End callback to complete + await Task.Delay(TimeSpan.FromSeconds(1)); + } + } + + Assert.Equal(2, startEndHandler.Invocations.Count); // begin and end was called + var spanData = ((Span)startEndHandler.Invocations[0].Arguments[0]).ToSpanData(); + + Assert.Equal(SpanKind.Server, spanData.Kind); + Assert.Equal("api/Values/{id}", spanData.Name); + Assert.Equal(AttributeValue.StringAttributeValue("/api/values/2"), spanData.Attributes.AttributeMap["http.path"]); + + Assert.Equal(expectedTraceId, spanData.Context.TraceId); + Assert.Equal(expectedSpanId, spanData.ParentSpanId); + } + } +} diff --git a/test/OpenCensus.Collector.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs b/test/OpenCensus.Collector.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs new file mode 100644 index 000000000..e68299230 --- /dev/null +++ b/test/OpenCensus.Collector.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs @@ -0,0 +1,104 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.AspNetCore.Tests +{ + using Xunit; + using Microsoft.AspNetCore.Mvc.Testing; + using TestApp.AspNetCore._2._0; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using OpenCensus.Trace; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using OpenCensus.Common; + using Moq; + using Microsoft.AspNetCore.TestHost; + using System; + using Microsoft.AspNetCore.Http; + + public class IncomingRequestsCollectionsIsAccordingToTheSpecTests + : IClassFixture> + { + private readonly WebApplicationFactory factory; + + public IncomingRequestsCollectionsIsAccordingToTheSpecTests(WebApplicationFactory factory) + { + this.factory = factory; + + } + + public class TestCallbackMiddlewareImpl : CallbackMiddleware.CallbackMiddlewareImpl + { + + public override async Task ProcessAsync(HttpContext context) + { + context.Response.StatusCode = 503; + await context.Response.WriteAsync("empty"); + return false; + } + } + + [Fact] + public async Task SuccesfulTemplateControllerCallGeneratesASpan() + { + var startEndHandler = new Mock(); + var tracer = new Tracer(new RandomGenerator(), startEndHandler.Object, new TraceConfig()); + + // Arrange + using (var client = this.factory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices((IServiceCollection services) => + { + services.AddSingleton(new TestCallbackMiddlewareImpl()); + services.AddSingleton(tracer); + })) + .CreateClient()) + { + + try + { + // Act + var response = await client.GetAsync("/api/values"); + } + catch (Exception ex) + { + // ignore errors + } + + for (int i = 0; i < 10; i++) + { + if (startEndHandler.Invocations.Count == 2) + { + break; + } + + // We need to let End callback execute as it is executed AFTER response was returned. + // In unit tests environment there may be a lot of parallel unit tests executed, so + // giving some breezing room for the End callback to complete + await Task.Delay(TimeSpan.FromSeconds(1)); + } + } + + Assert.Equal(2, startEndHandler.Invocations.Count); // begin and end was called + var spanData = ((Span)startEndHandler.Invocations[0].Arguments[0]).ToSpanData(); + + Assert.Equal(SpanKind.Server, spanData.Kind); + Assert.Equal(AttributeValue.StringAttributeValue("/api/values"), spanData.Attributes.AttributeMap["http.path"]); + Assert.Equal(AttributeValue.LongAttributeValue(503), spanData.Attributes.AttributeMap["http.status_code"]); + } + } +} diff --git a/test/OpenCensus.Collector.AspNetCore.Tests/OpenCensus.Collector.AspNetCore.Tests.csproj b/test/OpenCensus.Collector.AspNetCore.Tests/OpenCensus.Collector.AspNetCore.Tests.csproj new file mode 100644 index 000000000..5d33bdeeb --- /dev/null +++ b/test/OpenCensus.Collector.AspNetCore.Tests/OpenCensus.Collector.AspNetCore.Tests.csproj @@ -0,0 +1,54 @@ + + + + + Unit test project for OpenCensus + netcoreapp2.1 + + + + + + + + + PreserveNewest + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + All + + + + + + + + + + + + + + + + + + + + + + full + true + + \ No newline at end of file diff --git a/test/OpenCensus.Collector.AspNetCore.Tests/xunit.runner.json b/test/OpenCensus.Collector.AspNetCore.Tests/xunit.runner.json new file mode 100644 index 000000000..9fbc90115 --- /dev/null +++ b/test/OpenCensus.Collector.AspNetCore.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "maxParallelThreads": 1, + "parallelizeTestCollections": false +} \ No newline at end of file diff --git a/test/OpenCensus.Collector.Dependencies.Tests/BasicTests.cs b/test/OpenCensus.Collector.Dependencies.Tests/BasicTests.cs new file mode 100644 index 000000000..11d37aba7 --- /dev/null +++ b/test/OpenCensus.Collector.Dependencies.Tests/BasicTests.cs @@ -0,0 +1,91 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Tests +{ + using Moq; + using OpenCensus.Common; + using OpenCensus.Trace; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using OpenCensus.Trace.Propagation; + using OpenCensus.Trace.Sampler; + using System; + using System.Net.Http; + using System.Threading.Tasks; + using Xunit; + + public partial class HttpClientTests + { + [Fact] + public async Task HttpDepenenciesCollectorInjectsHeadersAsync() + { + var startEndHandler = new Mock(); + + var serverLifeTime = TestServer.RunServer( + (ctx) => + { + ctx.Response.StatusCode = 200; + ctx.Response.OutputStream.Close(); + }, + out string host, + out int port); + + var url = $"http://{host}:{port}/"; + + ITraceId expectedTraceId = TraceId.Invalid; + ISpanId expectedSpanId = SpanId.Invalid; + + using (serverLifeTime) + { + var tracer = new Tracer(new RandomGenerator(), startEndHandler.Object, new TraceConfig()); + + var tf = new Mock(); + tf + .Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback((ISpanContext sc, HttpRequestMessage obj, Action setter) => + { + expectedTraceId = sc.TraceId; + expectedSpanId = sc.SpanId; + }); + + var propagationComponent = new Mock(); + propagationComponent.SetupGet(m => m.TextFormat).Returns(tf.Object); + + using (var dc = new DependenciesCollector(new DependenciesCollectorOptions(), tracer, Samplers.AlwaysSample, propagationComponent.Object)) + { + + using (var c = new HttpClient()) + { + var request = new HttpRequestMessage + { + RequestUri = new Uri(url), + Method = new HttpMethod("GET"), + }; + + await c.SendAsync(request); + } + } + } + + Assert.Equal(2, startEndHandler.Invocations.Count); // begin and end was called + var spanData = ((Span)startEndHandler.Invocations[1].Arguments[0]).ToSpanData(); + + Assert.Equal(expectedTraceId, spanData.Context.TraceId); + Assert.Equal(expectedSpanId, spanData.Context.SpanId); + } + } +} diff --git a/test/OpenCensus.Collector.Dependencies.Tests/HttpClientTests.cs b/test/OpenCensus.Collector.Dependencies.Tests/HttpClientTests.cs new file mode 100644 index 000000000..1c044bb00 --- /dev/null +++ b/test/OpenCensus.Collector.Dependencies.Tests/HttpClientTests.cs @@ -0,0 +1,220 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Tests +{ + using Moq; + using Newtonsoft.Json; + using OpenCensus.Common; + using OpenCensus.Trace; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using OpenCensus.Trace.Propagation; + using OpenCensus.Trace.Sampler; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Reflection; + using System.Threading.Tasks; + using Xunit; + + public partial class HttpClientTests + { + public class HttpOutTestCase + { + public string name { get; set; } + + public string method { get; set; } + + public string url { get; set; } + + public Dictionary headers { get; set; } + + public int responseCode { get; set; } + + public string spanName { get; set; } + + public string spanKind { get; set; } + + public string spanStatus { get; set; } + + public Dictionary spanAttributes { get; set; } + } + + private static IEnumerable readTestCases() + { + Assembly assembly = Assembly.GetExecutingAssembly(); + var serializer = new JsonSerializer(); + var input = serializer.Deserialize(new JsonTextReader(new StreamReader(assembly.GetManifestResourceStream("OpenCensus.Collector.Dependencies.Tests.http-out-test-cases.json")))); + + return getArgumentsFromTestCaseObject(input); + } + + private static IEnumerable getArgumentsFromTestCaseObject(IEnumerable input) + { + List result = new List(); + + foreach (var testCase in input) + { + result.Add(new object[] { + testCase, + }); + } + + return result; + } + + public static IEnumerable TestData + { + get + { + return readTestCases(); + } + } + + [Theory] + [MemberData(nameof(TestData))] + public async Task HttpOutCallsAreCollectedSuccesfullyAsync(HttpOutTestCase tc) + { + var serverLifeTime = TestServer.RunServer( + (ctx) => + { + ctx.Response.StatusCode = tc.responseCode == 0 ? 200 : tc.responseCode; + ctx.Response.OutputStream.Close(); + }, + out string host, + out int port); + + var startEndHandler = new Mock(); + var tracer = new Tracer(new RandomGenerator(), startEndHandler.Object, new TraceConfig()); + tc.url = NormaizeValues(tc.url, host, port); + + using (serverLifeTime) + { + using (var dc = new DependenciesCollector(new DependenciesCollectorOptions(), tracer, Samplers.AlwaysSample, PropagationComponentBase.NoopPropagationComponent)) + { + + try + { + using (var c = new HttpClient()) + { + var request = new HttpRequestMessage + { + RequestUri = new Uri(tc.url), + Method = new HttpMethod(tc.method), + }; + + if (tc.headers != null) + { + foreach (var header in tc.headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + await c.SendAsync(request); + } + } + catch (Exception) + { + //test case can intentiaonlly send request that will result in exception + } + } + } + + Assert.Equal(2, startEndHandler.Invocations.Count); // begin and end was called + var spanData = ((Span)startEndHandler.Invocations[1].Arguments[0]).ToSpanData(); + + Assert.Equal(tc.spanName, spanData.Name); + Assert.Equal(tc.spanKind, spanData.Kind.ToString()); + + var d = new Dictionary() + { + { CanonicalCode.Ok, "OK"}, + { CanonicalCode.Cancelled, "CANCELLED"}, + { CanonicalCode.Unknown, "UNKNOWN"}, + { CanonicalCode.InvalidArgument, "INVALID_ARGUMENT"}, + { CanonicalCode.DeadlineExceeded, "DEADLINE_EXCEEDED"}, + { CanonicalCode.NotFound, "NOT_FOUND"}, + { CanonicalCode.AlreadyExists, "ALREADY_EXISTS"}, + { CanonicalCode.PermissionDenied, "PERMISSION_DENIED"}, + { CanonicalCode.ResourceExhausted, "RESOURCE_EXHAUSTED"}, + { CanonicalCode.FailedPrecondition, "FAILED_PRECONDITION"}, + { CanonicalCode.Aborted, "ABORTED"}, + { CanonicalCode.OutOfRange, "OUT_OF_RANGE"}, + { CanonicalCode.Unimplemented, "UNIMPLEMENTED"}, + { CanonicalCode.Internal, "INTERNAL"}, + { CanonicalCode.Unavailable, "UNAVAILABLE"}, + { CanonicalCode.DataLoss, "DATA_LOSS"}, + { CanonicalCode.Unauthenticated, "UNAUTHENTICATED"}, + }; + + Assert.Equal(tc.spanStatus, d[spanData.Status.CanonicalCode]); + + var normilizedAttributes = spanData.Attributes.AttributeMap.ToDictionary(x => x.Key, x => AttributeToSimpleString(x.Value)); + tc.spanAttributes = tc.spanAttributes.ToDictionary(x => x.Key, x => NormaizeValues(x.Value, host, port)); + + Assert.Equal(tc.spanAttributes.ToHashSet(), normilizedAttributes.ToHashSet()); + } + + [Fact] + public async Task DebugIndividualTestAsync() + { + var serializer = new JsonSerializer(); + var input = serializer.Deserialize(new JsonTextReader(new StringReader(@" +[ { + ""name"": ""Response code 404"", + ""method"": ""GET"", + ""url"": ""http://{host}:{port}/"", + ""responseCode"": 404, + ""spanName"": ""/"", + ""spanStatus"": ""NOT_FOUND"", + ""spanKind"": ""Client"", + ""spanAttributes"": { + ""http.path"": ""/"", + ""http.method"": ""GET"", + ""http.host"": ""{host}:{port}"", + ""http.status_code"": ""404"", + ""http.url"": ""http://{host}:{port}/"" +} + } +] +"))); + + var t = (Task)this.GetType().InvokeMember(nameof(HttpOutCallsAreCollectedSuccesfullyAsync), BindingFlags.InvokeMethod, null, this, getArgumentsFromTestCaseObject(input).First()); + await t; + } + + private string AttributeToSimpleString(IAttributeValue value) + { + return value.Match( + x => x.ToString(), + x => x ? "true" : "false", + x => x.ToString(), + x => x.ToString(), + x => x.ToString() + ); + } + + private string NormaizeValues(string value, string host, int port) + { + return value.Replace("{host}", host).Replace("{port}", port.ToString()); + } + + } +} diff --git a/test/OpenCensus.Collector.Dependencies.Tests/OpenCensus.Collector.Dependencies.Tests.csproj b/test/OpenCensus.Collector.Dependencies.Tests/OpenCensus.Collector.Dependencies.Tests.csproj new file mode 100644 index 000000000..d1d4a4ac1 --- /dev/null +++ b/test/OpenCensus.Collector.Dependencies.Tests/OpenCensus.Collector.Dependencies.Tests.csproj @@ -0,0 +1,57 @@ + + + + + Unit test project for OpenCensus + netcoreapp2.0 + + + + + + + + + PreserveNewest + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + All + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + full + true + + \ No newline at end of file diff --git a/test/OpenCensus.Collector.Dependencies.Tests/TestServer.cs b/test/OpenCensus.Collector.Dependencies.Tests/TestServer.cs new file mode 100644 index 000000000..68c5096ad --- /dev/null +++ b/test/OpenCensus.Collector.Dependencies.Tests/TestServer.cs @@ -0,0 +1,119 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.Dependencies.Tests +{ + using System; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + using Xunit; + + public class TestServer + { + private static Random GlobalRandom = new Random(); + + private class RunningServer : IDisposable + { + private readonly Task httpListenerTask; + private readonly HttpListener listener; + private readonly CancellationTokenSource cts; + private readonly AutoResetEvent initialized = new AutoResetEvent(false); + + public RunningServer(Action action, string host, int port) + { + this.cts = new CancellationTokenSource(); + this.listener = new HttpListener(); + + CancellationToken token = this.cts.Token; + + this.listener.Prefixes.Add($"http://{host}:{port}/"); + this.listener.Start(); + + this.httpListenerTask = new Task(() => + { + while (!token.IsCancellationRequested) + { + var ctxTask = this.listener.GetContextAsync(); + + this.initialized.Set(); + + try + { + ctxTask.Wait(token); + + if (ctxTask.Status == TaskStatus.RanToCompletion) + { + action(ctxTask.Result); + } + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + Assert.True(false, ex.ToString()); + } + } + }); + } + + public void Start() + { + this.httpListenerTask.Start(); + this.initialized.WaitOne(); + } + + public void Dispose() + { + try + { + this.listener?.Stop(); + cts.Cancel(); + } + catch (ObjectDisposedException) + { + // swallow this exception just in case + } + } + } + + public static IDisposable RunServer(Action action, out string host, out int port) + { + host = "localhost"; + port = 0; + RunningServer server = null; + + var retryCount = 5; + while (retryCount > 0) + { + try + { + port = GlobalRandom.Next(2000, 5000); + server = new RunningServer(action, host, port); + server.Start(); + break; + } + catch (HttpListenerException) + { + retryCount--; + } + } + + return server; + } + } +} diff --git a/test/OpenCensus.Collector.Dependencies.Tests/http-out-test-cases.json b/test/OpenCensus.Collector.Dependencies.Tests/http-out-test-cases.json new file mode 100644 index 000000000..c4a695ec0 --- /dev/null +++ b/test/OpenCensus.Collector.Dependencies.Tests/http-out-test-cases.json @@ -0,0 +1,274 @@ +[ + { + "name": "Successful GET call to https://example.com", + "method": "GET", + "url": "https://example.com/", + "spanName": "/", + "spanStatus": "OK", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "example.com", + "http.status_code": "200", + "http.url": "https://example.com/" + } + }, + { + "name": "Successfully POST call to https://example.com", + "method": "POST", + "url": "https://example.com/", + "spanName": "/", + "spanStatus": "OK", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "POST", + "http.host": "example.com", + "http.status_code": "200", + "http.url": "https://example.com/" + } + }, + { + "name": "Name is populated as a path", + "method": "GET", + "url": "http://{host}:{port}/path/to/resource/", + "responseCode": 200, + "spanName": "/path/to/resource/", + "spanStatus": "OK", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/path/to/resource/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "200", + "http.url": "http://{host}:{port}/path/to/resource/" + } + }, + { + "name": "Call that cannot resolve DNS will be reported as error span", + "method": "GET", + "url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/", + "spanName": "/", + "spanStatus": "UNKNOWN", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com", + "http.url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/" + } + }, + { + "name": "Response code: 199. This test case is not possible to implement on some platforms as they don't allow to return this status code. Keeping this test case for visibility, but it actually simply a fallback into 200 test case", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 200, + "spanName": "/", + "spanStatus": "OK", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "200", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 200", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 200, + "spanName": "/", + "spanStatus": "OK", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "200", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 399", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 399, + "spanName": "/", + "spanStatus": "OK", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "399", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 400", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 400, + "spanName": "/", + "spanStatus": "INVALID_ARGUMENT", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "400", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 401", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 401, + "spanName": "/", + "spanStatus": "UNAUTHENTICATED", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "401", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 403", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 403, + "spanName": "/", + "spanStatus": "PERMISSION_DENIED", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "403", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 404", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 404, + "spanName": "/", + "spanStatus": "NOT_FOUND", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "404", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 429", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 429, + "spanName": "/", + "spanStatus": "RESOURCE_EXHAUSTED", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "429", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 501", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 501, + "spanName": "/", + "spanStatus": "UNIMPLEMENTED", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "501", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 503", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 503, + "spanName": "/", + "spanStatus": "UNAVAILABLE", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "503", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 504", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 504, + "spanName": "/", + "spanStatus": "DEADLINE_EXCEEDED", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "504", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "Response code: 600", + "method": "GET", + "url": "http://{host}:{port}/", + "responseCode": 600, + "spanName": "/", + "spanStatus": "UNKNOWN", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "600", + "http.url": "http://{host}:{port}/" + } + }, + { + "name": "User agent attribute populated", + "method": "GET", + "url": "http://{host}:{port}/", + "headers": { + "User-Agent": "test-user-agent" + }, + "responseCode": 200, + "spanName": "/", + "spanStatus": "OK", + "spanKind": "Client", + "spanAttributes": { + "http.path": "/", + "http.method": "GET", + "http.host": "{host}:{port}", + "http.status_code": "200", + "http.user_agent": "test-user-agent", + "http.url": "http://{host}:{port}/" + } + } +] \ No newline at end of file diff --git a/test/OpenCensus.Collector.Dependencies.Tests/xunit.runner.json b/test/OpenCensus.Collector.Dependencies.Tests/xunit.runner.json new file mode 100644 index 000000000..9fbc90115 --- /dev/null +++ b/test/OpenCensus.Collector.Dependencies.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "maxParallelThreads": 1, + "parallelizeTestCollections": false +} \ No newline at end of file diff --git a/test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterSamplingTests.cs b/test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterSamplingTests.cs new file mode 100644 index 000000000..6a638945a --- /dev/null +++ b/test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterSamplingTests.cs @@ -0,0 +1,84 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.StackExchangeRedis.Implementation +{ + using Moq; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Internal; + using StackExchange.Redis.Profiling; + using System.Collections.Generic; + using Xunit; + + public class RedisProfilerEntryToSpanConverterSamplingTests + { + [Fact] + public void ShouldSampleRespectsSamplerChoice() + { + var m = new Mock(); + m.Setup(x => x.ShouldSample(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).Returns(true); + Assert.True(RedisProfilerEntryToSpanConverter.ShouldSample(SpanContext.Invalid, "SET", m.Object, out var context, out var parentId)); + + m = new Mock(); + m.Setup(x => x.ShouldSample(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).Returns(false); + Assert.False(RedisProfilerEntryToSpanConverter.ShouldSample(SpanContext.Invalid, "SET", m.Object, out context, out parentId)); + } + + [Fact] + public void ShouldSampleDoesntThrowWithoutSampler() + { + RedisProfilerEntryToSpanConverter.ShouldSample(SpanContext.Invalid, "SET", null, out var context, out var parentId); + } + + [Fact] + public void ShouldSamplePassesArgumentsToSamplerAndReturnsInContext() + { + var m = new Mock(); + var r = new RandomGenerator(); + var traceId = TraceId.GenerateRandomId(r); + var parentContext = SpanContext.Create(traceId, SpanId.GenerateRandomId(r), TraceOptions.Sampled, Tracestate.Builder.Set("a", "b").Build()); + RedisProfilerEntryToSpanConverter.ShouldSample(parentContext, "SET", m.Object, out var context, out var parentId); + + m.Verify(x => x.ShouldSample( + It.Is(y => y == parentContext), + It.Is(y => y == false), + It.Is(y => y == traceId && y == context.TraceId), + It.Is(y => y.IsValid && y == context.SpanId), + It.Is(y => y == "SET"), + It.Is>(y => y == null))); + } + + [Fact] + public void ShouldSampleGeneratesNewTraceIdForInvalidContext() + { + var m = new Mock(); + m.Setup(x => x.ShouldSample(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).Returns((ISpanContext parentContext, bool hasRemoteParent, ITraceId traceId, ISpanId spanId, string name, IEnumerable parentLinks) => parentContext.TraceOptions.IsSampled); + + RedisProfilerEntryToSpanConverter.ShouldSample(SpanContext.Invalid, "SET", m.Object, out var context, out var parentId); + + m.Verify(x => x.ShouldSample( + It.Is(y => y == SpanContext.Invalid), + It.Is(y => y == false), + It.Is(y => y.IsValid && y == context.TraceId), + It.Is(y => y.IsValid && y == context.SpanId), + It.Is(y => y == "SET"), + It.Is>(y => y == null))); + + Assert.Equal(TraceOptions.Default, context.TraceOptions); + } + } +} diff --git a/test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterTests.cs b/test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterTests.cs new file mode 100644 index 000000000..8a9cc6f2f --- /dev/null +++ b/test/OpenCensus.Collector.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToSpanConverterTests.cs @@ -0,0 +1,85 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.StackExchangeRedis.Implementation +{ + using OpenCensus.Trace; + using Moq; + using StackExchange.Redis.Profiling; + using Xunit; + using OpenCensus.Trace.Internal; + using System; + using OpenCensus.Common; + using System.Collections.Generic; + using OpenCensus.Trace.Export; + + public class RedisProfilerEntryToSpanConverterTests + { + [Fact] + public void DrainSessionUsesCommandAsName() + { + var parentSpan = BlankSpan.Instance; + var profiledCommand = new Mock(); + var sampler = new Mock(); + sampler.Setup(x => x.ShouldSample(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())).Returns(true); + profiledCommand.Setup(m => m.Command).Returns("SET"); + var result = new List(); + RedisProfilerEntryToSpanConverter.DrainSession(parentSpan, new IProfiledCommand[] { profiledCommand.Object }, sampler.Object, result); + Assert.Single(result); + Assert.Equal("SET", result[0].Name); + } + + [Fact] + public void ProfiledCommandToSpanDataUsesTimestampAsStartTime() + { + var profiledCommand = new Mock(); + var now = DateTimeOffset.Now; + profiledCommand.Setup(m => m.CommandCreated).Returns(now.DateTime); + var result = RedisProfilerEntryToSpanConverter.ProfiledCommandToSpanData(SpanContext.Invalid, "SET", SpanId.Invalid, profiledCommand.Object); + Assert.Equal(Timestamp.FromMillis(now.ToUnixTimeMilliseconds()), result.StartTimestamp); + } + + [Fact] + public void ProfiledCommandToSpanDataSetsDbTypeAttributeAsRedis() + { + var profiledCommand = new Mock(); + var result = RedisProfilerEntryToSpanConverter.ProfiledCommandToSpanData(SpanContext.Invalid, "SET", SpanId.Invalid, profiledCommand.Object); + Assert.Contains("db.type", result.Attributes.AttributeMap.Keys); + Assert.Equal(AttributeValue.StringAttributeValue("redis"), result.Attributes.AttributeMap["db.type"]); + } + + [Fact] + public void ProfiledCommandToSpanDataUsesCommandAsDbStatementAttribute() + { + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.Command).Returns("SET"); + var result = RedisProfilerEntryToSpanConverter.ProfiledCommandToSpanData(SpanContext.Invalid, "another name", SpanId.Invalid, profiledCommand.Object); + Assert.Contains("db.statement", result.Attributes.AttributeMap.Keys); + Assert.Equal(AttributeValue.StringAttributeValue("SET"), result.Attributes.AttributeMap["db.statement"]); + } + + [Fact] + public void ProfiledCommandToSpanDataUsesFlagsForFlagsAttribute() + { + var profiledCommand = new Mock(); + var expectedFlags = StackExchange.Redis.CommandFlags.FireAndForget | StackExchange.Redis.CommandFlags.NoRedirect; + profiledCommand.Setup(m => m.Flags).Returns(expectedFlags); + var result = RedisProfilerEntryToSpanConverter.ProfiledCommandToSpanData(SpanContext.Invalid, "SET", SpanId.Invalid, profiledCommand.Object); + Assert.Contains("redis.flags", result.Attributes.AttributeMap.Keys); + Assert.Equal(AttributeValue.StringAttributeValue("None, FireAndForget, NoRedirect"), result.Attributes.AttributeMap["redis.flags"]); + } + } +} diff --git a/test/OpenCensus.Collector.StackExchangeRedis.Tests/OpenCensus.Collector.StackExchangeRedis.Tests.csproj b/test/OpenCensus.Collector.StackExchangeRedis.Tests/OpenCensus.Collector.StackExchangeRedis.Tests.csproj new file mode 100644 index 000000000..63fe524b8 --- /dev/null +++ b/test/OpenCensus.Collector.StackExchangeRedis.Tests/OpenCensus.Collector.StackExchangeRedis.Tests.csproj @@ -0,0 +1,36 @@ + + + + + Unit test project for ApplicationInsights Exporter for OpenCensus + net461;netcoreapp2.0 + netcoreapp2.0 + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + full + true + + + \ No newline at end of file diff --git a/test/OpenCensus.Collector.StackExchangeRedis.Tests/StackExchangeRedisCallsCollectorTests.cs b/test/OpenCensus.Collector.StackExchangeRedis.Tests/StackExchangeRedisCallsCollectorTests.cs new file mode 100644 index 000000000..d787f832c --- /dev/null +++ b/test/OpenCensus.Collector.StackExchangeRedis.Tests/StackExchangeRedisCallsCollectorTests.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Collector.StackExchangeRedis +{ + using Moq; + using OpenCensus.Common; + using OpenCensus.Trace; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using StackExchange.Redis.Profiling; + using System.Threading.Tasks; + using Xunit; + + public class StackExchangeRedisCallsCollectorTests + { + [Fact] + public async void ProfilerSessionUsesTheSameDefault() + { + var startEndHandler = new Mock(); + var tracer = new Tracer(new RandomGenerator(), startEndHandler.Object, new TraceConfig()); + + using (var collector = new StackExchangeRedisCallsCollector(null, tracer, null, null)) + { + var profilerFactory = collector.GetProfilerSessionsFactory(); + var first = profilerFactory(); + var second = profilerFactory(); + + ProfilingSession third = null; + await Task.Delay(1).ContinueWith((t) => { third = profilerFactory(); }); + + Assert.Equal(first, second); + Assert.Equal(second, third); + } + } + } +} diff --git a/test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/StubTelemetryChannel.cs b/test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/StubTelemetryChannel.cs new file mode 100644 index 000000000..5e7e1c713 --- /dev/null +++ b/test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/StubTelemetryChannel.cs @@ -0,0 +1,96 @@ +// +// Copyright 2018, OpenCensus 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 theLicense 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. +// + +namespace OpenCensus.Exporter.ApplicationInsights.Tests +{ + using Microsoft.ApplicationInsights.Channel; + using System; + + /// + /// A stub of . + /// + public sealed class StubTelemetryChannel : ITelemetryChannel + { + /// + /// Initializes a new instance of the class. + /// + public StubTelemetryChannel() + { + this.OnSend = telemetry => { }; + this.OnFlush = () => { }; + this.OnDispose = () => { }; + } + + /// + /// Gets or sets a value indicating whether this channel is in developer mode. + /// + public bool? DeveloperMode { get; set; } + + /// + /// Gets or sets a value indicating the channel's URI. To this URI the telemetry is expected to be sent. + /// + public string EndpointAddress { get; set; } + + /// + /// Gets or sets a value indicating whether to throw an error. + /// + public bool ThrowError { get; set; } + + /// + /// Gets or sets the callback invoked by the method. + /// + public Action OnSend { get; set; } + + /// + /// Gets or sets the callback invoked by the method. + /// + public Action OnFlush { get; set; } + + /// + /// Gets or sets the callback invoked by the method. + /// + public Action OnDispose { get; set; } + + /// + /// Implements the method by invoking the callback. + /// + public void Send(ITelemetry item) + { + if (this.ThrowError) + { + throw new Exception("test error"); + } + + this.OnSend(item); + } + + /// + /// Implements the method. + /// + public void Dispose() + { + this.OnDispose(); + } + + /// + /// Implements the method. + /// + public void Flush() + { + this.OnFlush(); + } + } +} diff --git a/test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs b/test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs new file mode 100644 index 000000000..96034a610 --- /dev/null +++ b/test/OpenCensus.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs @@ -0,0 +1,1873 @@ +// +// Copyright 2018, OpenCensus 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 theLicense 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. +// + +namespace OpenCensus.Exporter.ApplicationInsights.Tests +{ + using Microsoft.ApplicationInsights.Channel; + using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.Extensibility.Implementation; + using OpenCensus.Common; + using OpenCensus.Exporter.ApplicationInsights.Implementation; + using OpenCensus.Trace; + using OpenCensus.Trace.Export; + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Threading; + using Xunit; + + public class OpenCensusTelemetryConverterTests + { + private const string TestTraceId = "d79bdda7eb9c4a9fa9bda52fe7b48b95"; + private const string TestSpanId = "d7ddeb4aa9a5e78b"; + private const string TestParentSpanId = "9ba79c9fbd2fb495"; + + private readonly byte[] testTraceIdBytes = { 0xd7, 0x9b, 0xdd, 0xa7, 0xeb, 0x9c, 0x4a, 0x9f, 0xa9, 0xbd, 0xa5, 0x2f, 0xe7, 0xb4, 0x8b, 0x95 }; + private readonly byte[] testSpanIdBytes = { 0xd7, 0xdd, 0xeb, 0x4a, 0xa9, 0xa5, 0xe7, 0x8b }; + private readonly byte[] testParentSpanIdBytes = { 0x9b, 0xa7, 0x9c, 0x9f, 0xbd, 0x2f, 0xb4, 0x95 }; + + private DateTimeOffset nowDateTimeOffset; + + private Timestamp NowTimestamp + { + get + { + return Timestamp.FromDateTimeOffset(nowDateTimeOffset); + } + } + + public OpenCensusTelemetryConverterTests() + { + nowDateTimeOffset = DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(1)); + } + + private ConcurrentQueue ConvertSpan(ISpanData data) + { + var sentItems = new ConcurrentQueue(); + var configuration = new TelemetryConfiguration(); + ITelemetryChannel channel = new StubTelemetryChannel + { + OnSend = t => sentItems.Enqueue(t), + }; + + configuration.TelemetryChannel = channel; + + TraceExporterHandler exporter = new TraceExporterHandler(configuration); + exporter.ExportAsync(new List { data }).Wait(); + + return sentItems; + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequest() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Single(sentItems); + Assert.True(sentItems.Single() is RequestTelemetry); + + var request = sentItems.OfType().Single(); + Assert.Equal("spanName", request.Name); + Assert.Equal(nowDateTimeOffset.Subtract(TimeSpan.FromSeconds(1)), request.Timestamp); + Assert.Equal(1, request.Duration.TotalSeconds); + + Assert.Equal(TestTraceId, request.Context.Operation.Id); + Assert.Null(request.Context.Operation.ParentId); + + Assert.Equal($"|{TestTraceId}.{TestSpanId}.", request.Id); + + Assert.False(request.Success.HasValue); + Assert.True(string.IsNullOrEmpty(request.ResponseCode)); + + // TODO: implement this + //Assert.Equal("lf_unspecified-oc:0.0.0", request.Context.GetInternalContext().SdkVersion); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithTracestate() + { + // ARRANGE + + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + context = SpanContext.Create( + context.TraceId, + context.SpanId, + context.TraceOptions, + tracestate: context.Tracestate.ToBuilder() + .Set("k1", "v1") + .Set("k2", "v2") + .Build()); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Single(sentItems); + Assert.True(sentItems.Single() is RequestTelemetry); + + var request = sentItems.OfType().Single(); + + Assert.Equal(2, request.Properties.Count); + Assert.Equal("v1", request.Properties["k1"]); + Assert.Equal("v2", request.Properties["k2"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithParent() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + parentSpanId = SpanId.FromBytes(this.testParentSpanIdBytes); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Equal($"|{TestTraceId}.{TestParentSpanId}.", ((RequestTelemetry)sentItems.Single()).Context.Operation.ParentId); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithInvalidParent() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + parentSpanId = SpanId.Invalid; + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Null(((RequestTelemetry)sentItems.Single()).Context.Operation.ParentId); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithStatus() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + status = Status.Ok; + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var request = (RequestTelemetry)sentItems.Single(); + + Assert.True(request.Success.HasValue); + Assert.True(request.Success.Value); + Assert.Equal("0", request.ResponseCode); // this check doesn't match Local Forwarder Assert.IsTrue(string.IsNullOrEmpty(request.ResponseCode)); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithStatusAndDescription() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + status = Status.Ok.WithDescription("all good"); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var request = (RequestTelemetry)sentItems.Single(); + + Assert.True(request.Success.HasValue); + Assert.True(request.Success.Value); + Assert.Equal("0", request.ResponseCode); // this check doesn't match Local Forwarder Assert.AreEqual("all good", request.ResponseCode); + Assert.Equal("all good", request.Properties["statusDescription"]); // this check doesn't match Local Forwarder + } + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithNonSuccessStatusAndDescription() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + status = Status.Cancelled.WithDescription("all bad"); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var request = (RequestTelemetry)sentItems.Single(); + + Assert.True(request.Success.HasValue); + Assert.False(request.Success.Value); + Assert.Equal("1", request.ResponseCode); // this check doesn't match Local Forwarder Assert.AreEqual("all bad", request.ResponseCode); + Assert.Equal("all bad", request.Properties["statusDescription"]); // this check doesn't match Local Forwarder + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestErrorAttribute() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + attributes = Attributes.Create(new Dictionary() { { "error", AttributeValue.BooleanAttributeValue(true) } }, 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.True(request.Success.HasValue); + Assert.False(request.Success.Value); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependency() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + kind = SpanKind.Client; + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Single(sentItems); + Assert.True(sentItems.Single() is DependencyTelemetry); + + var dependency = sentItems.OfType().Single(); + Assert.Equal("spanName", dependency.Name); + Assert.Equal(nowDateTimeOffset.Subtract(TimeSpan.FromSeconds(1)), dependency.Timestamp); + Assert.Equal(1, dependency.Duration.TotalSeconds); + + Assert.Equal(TestTraceId, dependency.Context.Operation.Id); + Assert.Null(dependency.Context.Operation.ParentId); + Assert.Equal($"|{TestTraceId}.{TestSpanId}.", dependency.Id); + + Assert.True(string.IsNullOrEmpty(dependency.ResultCode)); + Assert.False(dependency.Success.HasValue); + + // Assert.Equal("lf_unspecified-oc:0.0.0", dependency.Context.GetInternalContext().SdkVersion); + + Assert.True(string.IsNullOrEmpty(dependency.Type)); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithTracestate() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + kind = SpanKind.Client; + context = SpanContext.Create( + context.TraceId, + context.SpanId, + context.TraceOptions, + tracestate: context.Tracestate.ToBuilder() + .Set("k1", "v1") + .Set("k2", "v2") + .Build()); + + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Single(sentItems); + Assert.True(sentItems.Single() is DependencyTelemetry); + + var dependency = sentItems.OfType().Single(); + + Assert.Equal(2, dependency.Properties.Count); + Assert.Equal("v1", dependency.Properties["k1"]); + Assert.Equal("v2", dependency.Properties["k2"]); + // Assert.Equal("lf_unspecified-oc:0.0.0", dependency.Context.GetInternalContext().SdkVersion); + } + + /* + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithAnnotationsAndNode() + { + // ARRANGE + var now = DateTime.UtcNow; + + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + kind = SpanKind.Client; + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + + span.TimeEvents = new Span.Types.TimeEvents + { + TimeEvent = + { + new Span.Types.TimeEvent + { + Time = now.ToTimestamp(), + Annotation = new Span.Types.TimeEvent.Types.Annotation + { + Description = new TruncatableString {Value = "test message1"}, + }, + }, + new Span.Types.TimeEvent + { + Time = now.ToTimestamp(), + MessageEvent = new Span.Types.TimeEvent.Types.MessageEvent + { + Id = 1, + CompressedSize = 2, + UncompressedSize = 3, + Type = Span.Types.TimeEvent.Types.MessageEvent.Types.Type.Received, + }, + }, + }, + }; + + + string hostName = "host", serviceName = "tests", version = "1.2.3.4.5"; + uint pid = 12345; + var lang = LibraryInfo.Types.Language.CSharp; + + var node = CreateBasicNode(hostName, pid, lang, version, serviceName); + node.Attributes.Add("a", "b"); + + // ACT + var sentItems = this.ConvertSpan(span, node, string.Empty); + + // ASSERT + Assert.Equal(3, sentItems.Count); + Assert.Single(sentItems.OfType()); + Assert.Equal(2, sentItems.OfType().Count()); + + var dependency = sentItems.OfType().Single(); + var trace1 = sentItems.OfType().First(); + var trace2 = sentItems.OfType().Last(); + Assert.Equal(serviceName, dependency.Context.Cloud.RoleName); + Assert.Equal(serviceName, trace1.Context.Cloud.RoleName); + Assert.Equal(serviceName, trace2.Context.Cloud.RoleName); + + Assert.Equal($"{hostName}.{pid}", dependency.Context.Cloud.RoleInstance); + Assert.Equal($"{hostName}.{pid}", trace1.Context.Cloud.RoleInstance); + Assert.Equal($"{hostName}.{pid}", trace2.Context.Cloud.RoleInstance); + + Assert.Equal(0, dependency.Properties.Count); + Assert.Equal(0, trace1.Properties.Count); + Assert.Equal(0, trace2.Properties.Count); + } + */ + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithParent() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Client; + parentSpanId = SpanId.FromBytes(this.testParentSpanIdBytes); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var dependency = sentItems.OfType().Single(); + Assert.Equal($"|{TestTraceId}.{TestParentSpanId}.", dependency.Context.Operation.ParentId); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithStatus() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Client; + status = Status.Ok; + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var dependency = (DependencyTelemetry)sentItems.Single(); + + Assert.True(dependency.Success.HasValue); + Assert.True(dependency.Success.Value); + Assert.Equal("0", dependency.ResultCode); + Assert.False(dependency.Properties.ContainsKey("StatusDescription")); // TODO: why it is upper case first letter? + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithStatusAndDescription() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Client; + status = Status.Ok.WithDescription("all good"); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var dependency = (DependencyTelemetry)sentItems.Single(); + + Assert.True(dependency.Success.HasValue); + Assert.True(dependency.Success.Value); + + Assert.Equal("0", dependency.ResultCode); + Assert.True(dependency.Properties.ContainsKey("statusDescription")); + Assert.Equal("all good", dependency.Properties["statusDescription"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithNonSuccessStatusAndDescription() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Client; + status = Status.Cancelled.WithDescription("all bad"); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var dependency = (DependencyTelemetry)sentItems.Single(); + + Assert.True(dependency.Success.HasValue); + Assert.False(dependency.Success.Value); + Assert.Equal("1", dependency.ResultCode); + Assert.True(dependency.Properties.ContainsKey("statusDescription")); + Assert.Equal("all bad", dependency.Properties["statusDescription"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyErrorAttribute() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() { { "error", AttributeValue.BooleanAttributeValue(true) } }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.True(dependency.Success.HasValue); + Assert.False(dependency.Success.Value); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestBasedOnSpanKindAttribute() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() { { "span.kind", AttributeValue.StringAttributeValue("server") } }, 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is RequestTelemetry); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestBasedOnSpanKindProperty() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + parentSpanId = SpanId.FromBytes(this.testParentSpanIdBytes); + hasRemoteParent = null; + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is RequestTelemetry); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyBasedOnSpanKindProperty() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Client; + parentSpanId = SpanId.FromBytes(this.testParentSpanIdBytes); + hasRemoteParent = null; + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is DependencyTelemetry); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependenciesBasedOnSpanKindAttribute() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Unspecified; + attributes = Attributes.Create(new Dictionary() { { "span.kind", AttributeValue.StringAttributeValue("client") } }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is DependencyTelemetry); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestBasedOnSameProcessAsParentFlag() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Unspecified; + hasRemoteParent = true; + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is RequestTelemetry); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDepednencyBasedOnSameProcessAsParentFlag() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Unspecified; + hasRemoteParent = false; + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is DependencyTelemetry); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDepednencyBasedOnSameProcessAsParentFlagNotSet() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Unspecified; + hasRemoteParent = null; + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is DependencyTelemetry); + } + + // TODO: should we allow null dates? There is no reason to not allow it + //[Fact] + //public void OpenCensusTelemetryConverterTests_TracksRequestWithoutName() + //{ + // this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + // name = null; + // var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // var sentItems = this.ConvertSpan(span); + + // Assert.Null(sentItems.OfType().Single().Name); + //} + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithoutKind() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + kind = SpanKind.Unspecified; + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.True(sentItems.Single() is DependencyTelemetry); + } + + // TODO: should we allow null dates? There is no reason to not allow it + //[Fact] + //public void OpenCensusTelemetryConverterTests_TracksRequestWithoutStartAndEndTime() + //{ + // this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + // startTimestamp = null; + // endTimestamp = null; + // var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // var sentItems = this.ConvertSpan(span); + + // var request = sentItems.OfType().Single(); + // Assert.True(Math.Abs((request.Timestamp - DateTime.UtcNow).TotalSeconds) < 1); + // Assert.Equal(0, request.Duration.TotalSeconds); + //} + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestWithUrl() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.ToString()) }, + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.status_code", AttributeValue.LongAttributeValue(409) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal(url.ToString(), request.Url.ToString()); + Assert.Equal("POST /path", request.Name); + Assert.Equal("409", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestWithRelativeUrl() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.LocalPath) }, + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.status_code", AttributeValue.LongAttributeValue(409) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal("/path", request.Url.ToString()); // This check doesn't match Local Forwarder Assert.Null(request.Url); + Assert.Equal("POST /path", request.Name); + Assert.Equal("409", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestWithUrlAndRoute() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.ToString()) }, + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.route", AttributeValue.StringAttributeValue("route") }, + { "http.status_code", AttributeValue.LongAttributeValue(503) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal(url.ToString(), request.Url.ToString()); + Assert.Equal("POST route", request.Name); + Assert.Equal("503", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestWithUrlAndNoMethod() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.ToString()) }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal(url.ToString(), request.Url.ToString()); + Assert.Equal("/path", request.Name); + Assert.Equal("200", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestWithUrlOtherAttributesAreIgnored() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.ToString()) }, + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.path", AttributeValue.StringAttributeValue("another path") }, + { "http.host", AttributeValue.StringAttributeValue("another host") }, + { "http.port", AttributeValue.LongAttributeValue(8080) }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal(url.ToString(), request.Url.ToString()); + Assert.Equal("POST another path", request.Name); // This check doesn't match Local Forwarder Assert.AreEqual("POST /path", request.Name); + Assert.Equal("200", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithStringStatusCode() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.status_code", AttributeValue.LongAttributeValue(201) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var request = (RequestTelemetry)sentItems.Single(); + + Assert.Equal("201", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestHostPortPathAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.path", AttributeValue.StringAttributeValue("path") }, + { "http.host", AttributeValue.StringAttributeValue("host") }, + { "http.port", AttributeValue.LongAttributeValue(123) }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal("https://host:123/path", request.Url.ToString()); + Assert.Equal("POST path", request.Name); + Assert.Equal("200", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestPortPathAndEmptyHostAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.path", AttributeValue.StringAttributeValue("path") }, + { "http.host", AttributeValue.StringAttributeValue("") }, + { "http.port", AttributeValue.LongAttributeValue(123) }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal("/path", request.Url.ToString()); // This check doesn't match Local Forwarder Assert.IsNull(request.Url); + Assert.Equal("POST path", request.Name); + Assert.Equal("200", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestHostPathAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.path", AttributeValue.StringAttributeValue("/path") }, + { "http.host", AttributeValue.StringAttributeValue("host") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal("https://host/path", request.Url.ToString()); + Assert.Equal("POST /path", request.Name); + Assert.Equal("200", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestHostAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.host", AttributeValue.StringAttributeValue("host") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal("https://host/", request.Url.ToString()); + Assert.Equal("POST", request.Name); + Assert.Equal("200", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestOnlyMethodAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Null(request.Url); + Assert.Equal("POST", request.Name); + Assert.Equal("200", request.ResponseCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithStringStatusCode() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + kind = SpanKind.Client; + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.status_code", AttributeValue.LongAttributeValue(201) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + var dependency = (DependencyTelemetry)sentItems.Single(); + + Assert.Equal("201", dependency.ResultCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpRequestUserAgent() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host/path"); + var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"; + name = "HttpIn"; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.ToString()) }, + { "http.user_agent", AttributeValue.StringAttributeValue(userAgent) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal(userAgent, request.Context.User.UserAgent); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithUrl() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.ToString()) }, + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal(url.ToString(), dependency.Data); + Assert.Equal("POST /path", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.Equal("host", dependency.Target); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithRelativeUrl() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.LocalPath) }, + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal(url.LocalPath, dependency.Data); + Assert.Equal("POST /path", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.Null(dependency.Target); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithUrlIgnoresHostPortPath() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.url", AttributeValue.StringAttributeValue(url.ToString()) }, + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.path", AttributeValue.StringAttributeValue("another path") }, + { "http.host", AttributeValue.StringAttributeValue("another host") }, + { "http.port", AttributeValue.LongAttributeValue(8080) }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal(url.ToString(), dependency.Data); + Assert.Equal("POST another path", dependency.Name); // This check doesn't match Local Forwarder Assert.AreEqual("POST /path", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.Equal("host", dependency.Target); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithHostPortPath() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.path", AttributeValue.StringAttributeValue("/path") }, + { "http.host", AttributeValue.StringAttributeValue("host") }, + { "http.port", AttributeValue.LongAttributeValue(123) }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal("https://host:123/path", dependency.Data); + Assert.Equal("POST /path", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.Equal("host", dependency.Target); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithHostPort() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.host", AttributeValue.StringAttributeValue("host") }, + { "http.port", AttributeValue.LongAttributeValue(123) }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal("https://host:123/", dependency.Data); + Assert.Equal("POST", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.Equal("host", dependency.Target); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithPathAndEmptyHost() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.path", AttributeValue.StringAttributeValue("/path") }, + { "http.host", AttributeValue.StringAttributeValue("") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal("/path", dependency.Data); // This check doesn't match Local Forwarder Assert.IsNull(dependency.Data); + Assert.Equal("POST /path", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.True(string.IsNullOrEmpty(dependency.Target)); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithHost() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.host", AttributeValue.StringAttributeValue("host") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal("https://host/", dependency.Data); + Assert.Equal("POST", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.Equal("host", dependency.Target); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithMethod() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.method", AttributeValue.StringAttributeValue("POST") }, + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Null(dependency.Data); + Assert.Equal("POST", dependency.Name); + Assert.Equal("200", dependency.ResultCode); + Assert.Null(dependency.Target); + Assert.Equal("Http", dependency.Type); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHttpDependencyWithStatusCodeOnly() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "HttpOut"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "http.status_code", AttributeValue.LongAttributeValue(200) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Null(dependency.Data); + Assert.Equal("HttpOut", dependency.Name); // This check doesn't match Local Forwarder + Assert.Null(dependency.Target); + Assert.Equal("Http", dependency.Type); + Assert.Equal("200", dependency.ResultCode); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithCustomAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "spanName"; + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary() + { + { "custom.stringAttribute", AttributeValue.StringAttributeValue("string") }, + { "custom.longAttribute", AttributeValue.LongAttributeValue(long.MaxValue) }, + { "custom.boolAttribute", AttributeValue.BooleanAttributeValue(true) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal("spanName", dependency.Name); + Assert.Equal(3, dependency.Properties.Count); + Assert.True(dependency.Properties.ContainsKey("custom.stringAttribute")); + Assert.Equal("string", dependency.Properties["custom.stringAttribute"]); + + Assert.True(dependency.Properties.ContainsKey("custom.longAttribute")); + Assert.Equal(long.MaxValue.ToString(), dependency.Properties["custom.longAttribute"]); + + Assert.True(dependency.Properties.ContainsKey("custom.boolAttribute")); + Assert.Equal(bool.TrueString, dependency.Properties["custom.boolAttribute"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestsWithCustomAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + var url = new Uri("https://host:123/path?query"); + name = "spanName"; + kind = SpanKind.Server; + attributes = Attributes.Create(new Dictionary() + { + { "custom.stringAttribute", AttributeValue.StringAttributeValue("string") }, + { "custom.longAttribute", AttributeValue.LongAttributeValue(long.MaxValue) }, + { "custom.boolAttribute", AttributeValue.BooleanAttributeValue(true) }, + }, 0); + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal("spanName", request.Name); + Assert.Equal(3, request.Properties.Count); + Assert.True(request.Properties.ContainsKey("custom.stringAttribute")); + Assert.Equal("string", request.Properties["custom.stringAttribute"]); + + Assert.True(request.Properties.ContainsKey("custom.longAttribute")); + Assert.Equal(long.MaxValue.ToString(), request.Properties["custom.longAttribute"]); + + Assert.True(request.Properties.ContainsKey("custom.boolAttribute")); + Assert.Equal(bool.TrueString, request.Properties["custom.boolAttribute"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithLinks() + { + var (link0TraceId, link0TraceIdBytes) = GenerateRandomId(16); + var (link1TraceId, link1TraceIdBytes) = GenerateRandomId(16); + var (link2TraceId, link2TraceIdBytes) = GenerateRandomId(16); + + var (link0SpanId, link0SpanIdBytes) = GenerateRandomId(8); + var (link1SpanId, link1SpanIdBytes) = GenerateRandomId(8); + var (link2SpanId, link2SpanIdBytes) = GenerateRandomId(8); + + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "spanName"; + kind = SpanKind.Client; + + links = LinkList.Create(new List() { + Link.FromSpanContext( + SpanContext.Create(TraceId.FromBytes(link0TraceIdBytes), SpanId.FromBytes(link0SpanIdBytes), TraceOptions.Default, Tracestate.Empty), LinkType.ChildLinkedSpan), + Link.FromSpanContext( + SpanContext.Create(TraceId.FromBytes(link1TraceIdBytes), SpanId.FromBytes(link1SpanIdBytes), TraceOptions.Default, Tracestate.Empty), LinkType.ParentLinkedSpan), + Link.FromSpanContext( + SpanContext.Create(TraceId.FromBytes(link2TraceIdBytes), SpanId.FromBytes(link2SpanIdBytes), TraceOptions.Default, Tracestate.Empty), LinkType.Unspecified), + }, 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal(9, dependency.Properties.Count); + + Assert.True(dependency.Properties.ContainsKey("link0_traceId")); + Assert.True(dependency.Properties.ContainsKey("link1_traceId")); + Assert.True(dependency.Properties.ContainsKey("link2_traceId")); + + Assert.Equal(link0TraceId, dependency.Properties["link0_traceId"]); + Assert.Equal(link1TraceId, dependency.Properties["link1_traceId"]); + Assert.Equal(link2TraceId, dependency.Properties["link2_traceId"]); + + Assert.True(dependency.Properties.ContainsKey("link0_spanId")); + Assert.True(dependency.Properties.ContainsKey("link1_spanId")); + Assert.True(dependency.Properties.ContainsKey("link2_spanId")); + + Assert.Equal(link0SpanId, dependency.Properties["link0_spanId"]); + Assert.Equal(link1SpanId, dependency.Properties["link1_spanId"]); + Assert.Equal(link2SpanId, dependency.Properties["link2_spanId"]); + + Assert.True(dependency.Properties.ContainsKey("link0_type")); + Assert.True(dependency.Properties.ContainsKey("link1_type")); + Assert.True(dependency.Properties.ContainsKey("link2_type")); + + Assert.Equal("ChildLinkedSpan", dependency.Properties["link0_type"]); + Assert.Equal("ParentLinkedSpan", dependency.Properties["link1_type"]); + Assert.Equal("Unspecified", dependency.Properties["link2_type"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithLinksAndAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "spanName"; + kind = SpanKind.Client; + + links = LinkList.Create( + new List() { + Link.FromSpanContext( + SpanContext.Create( + TraceId.FromBytes(GenerateRandomId(16).Item2), + SpanId.FromBytes(GenerateRandomId(8).Item2), + TraceOptions.Default, + Tracestate.Empty), + LinkType.ChildLinkedSpan, + new Dictionary() + { + { "some.str.attribute", AttributeValue.StringAttributeValue("foo") }, + { "some.int.attribute", AttributeValue.LongAttributeValue(1) }, + { "some.bool.attribute", AttributeValue.BooleanAttributeValue(true) }, + }), + }, + droppedLinksCount: 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var dependency = sentItems.OfType().Single(); + Assert.Equal(6, dependency.Properties.Count); + + Assert.True(dependency.Properties.ContainsKey("link0_some.str.attribute")); + Assert.Equal("foo", dependency.Properties["link0_some.str.attribute"]); + + Assert.True(dependency.Properties.ContainsKey("link0_some.int.attribute")); + Assert.Equal("1", dependency.Properties["link0_some.int.attribute"]); + + Assert.True(dependency.Properties.ContainsKey("link0_some.bool.attribute")); + Assert.Equal(bool.TrueString, dependency.Properties["link0_some.bool.attribute"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithLinks() + { + var (link0TraceId, link0TraceIdBytes) = GenerateRandomId(16); + var (link1TraceId, link1TraceIdBytes) = GenerateRandomId(16); + var (link2TraceId, link2TraceIdBytes) = GenerateRandomId(16); + + var (link0SpanId, link0SpanIdBytes) = GenerateRandomId(8); + var (link1SpanId, link1SpanIdBytes) = GenerateRandomId(8); + var (link2SpanId, link2SpanIdBytes) = GenerateRandomId(8); + + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "spanName"; + kind = SpanKind.Server; + + links = LinkList.Create(new List() { + Link.FromSpanContext( + SpanContext.Create(TraceId.FromBytes(link0TraceIdBytes), SpanId.FromBytes(link0SpanIdBytes), TraceOptions.Default, Tracestate.Empty), LinkType.ChildLinkedSpan), + Link.FromSpanContext( + SpanContext.Create(TraceId.FromBytes(link1TraceIdBytes), SpanId.FromBytes(link1SpanIdBytes), TraceOptions.Default, Tracestate.Empty), LinkType.ParentLinkedSpan), + Link.FromSpanContext( + SpanContext.Create(TraceId.FromBytes(link2TraceIdBytes), SpanId.FromBytes(link2SpanIdBytes), TraceOptions.Default, Tracestate.Empty), LinkType.Unspecified), + }, 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal(9, request.Properties.Count); + + Assert.True(request.Properties.ContainsKey("link0_traceId")); + Assert.True(request.Properties.ContainsKey("link1_traceId")); + Assert.True(request.Properties.ContainsKey("link2_traceId")); + + Assert.Equal(link0TraceId, request.Properties["link0_traceId"]); + Assert.Equal(link1TraceId, request.Properties["link1_traceId"]); + Assert.Equal(link2TraceId, request.Properties["link2_traceId"]); + + Assert.True(request.Properties.ContainsKey("link0_spanId")); + Assert.True(request.Properties.ContainsKey("link1_spanId")); + Assert.True(request.Properties.ContainsKey("link2_spanId")); + + Assert.Equal(link0SpanId, request.Properties["link0_spanId"]); + Assert.Equal(link1SpanId, request.Properties["link1_spanId"]); + Assert.Equal(link2SpanId, request.Properties["link2_spanId"]); + + Assert.True(request.Properties.ContainsKey("link0_type")); + Assert.True(request.Properties.ContainsKey("link1_type")); + Assert.True(request.Properties.ContainsKey("link2_type")); + + Assert.Equal("ChildLinkedSpan", request.Properties["link0_type"]); + Assert.Equal("ParentLinkedSpan", request.Properties["link1_type"]); + Assert.Equal("Unspecified", request.Properties["link2_type"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithLinksAndAttributes() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + name = "spanName"; + kind = SpanKind.Server; + + links = LinkList.Create( + new List() { + Link.FromSpanContext( + SpanContext.Create( + TraceId.FromBytes(GenerateRandomId(16).Item2), + SpanId.FromBytes(GenerateRandomId(8).Item2), + TraceOptions.Default, + Tracestate.Empty), + LinkType.ChildLinkedSpan, + new Dictionary() + { + { "some.str.attribute", AttributeValue.StringAttributeValue("foo") }, + { "some.int.attribute", AttributeValue.LongAttributeValue(1) }, + { "some.bool.attribute", AttributeValue.BooleanAttributeValue(true) }, + }), + }, + droppedLinksCount: 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + var request = sentItems.OfType().Single(); + Assert.Equal(6, request.Properties.Count); + + Assert.True(request.Properties.ContainsKey("link0_some.str.attribute")); + Assert.Equal("foo", request.Properties["link0_some.str.attribute"]); + + Assert.True(request.Properties.ContainsKey("link0_some.int.attribute")); + Assert.Equal("1", request.Properties["link0_some.int.attribute"]); + + Assert.True(request.Properties.ContainsKey("link0_some.bool.attribute")); + Assert.Equal(bool.TrueString, request.Properties["link0_some.bool.attribute"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithAnnotations() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + Thread.Sleep(TimeSpan.FromTicks(10)); + name = "spanName"; + kind = SpanKind.Server; + + annotations = TimedEvents.Create( + new List>() { + TimedEvent.Create(NowTimestamp, Annotation.FromDescription("test message1")), + TimedEvent.Create(null, Annotation.FromDescriptionAndAttributes("test message2", new Dictionary() + { + { "custom.stringAttribute", AttributeValue.StringAttributeValue("string") }, + { "custom.longAttribute", AttributeValue.LongAttributeValue(long.MaxValue) }, + { "custom.boolAttribute", AttributeValue.BooleanAttributeValue(true) }, + })), + }, + droppedEventsCount: 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.Equal(3, sentItems.Count); + Assert.Single(sentItems.OfType()); + Assert.Equal(2, sentItems.OfType().Count()); + + var request = sentItems.OfType().Single(); + var trace1 = sentItems.OfType().First(); + var trace2 = sentItems.OfType().Last(); + + Assert.Equal(request.Context.Operation.Id, trace1.Context.Operation.Id); + Assert.Equal(request.Context.Operation.Id, trace2.Context.Operation.Id); + Assert.Equal(request.Id, trace1.Context.Operation.ParentId); + Assert.Equal(request.Id, trace2.Context.Operation.ParentId); + + Assert.Equal("test message1", trace1.Message); + Assert.Equal("test message2", trace2.Message); + + Assert.Equal(nowDateTimeOffset, trace1.Timestamp); + Assert.NotEqual(nowDateTimeOffset, trace2.Timestamp); + Assert.True(Math.Abs((DateTime.UtcNow - trace2.Timestamp).TotalSeconds) < 1); + + Assert.False(trace1.Properties.Any()); + Assert.Equal(3, trace2.Properties.Count); + Assert.True(trace2.Properties.ContainsKey("custom.stringAttribute")); + Assert.Equal("string", trace2.Properties["custom.stringAttribute"]); + + Assert.True(trace2.Properties.ContainsKey("custom.longAttribute")); + Assert.Equal(long.MaxValue.ToString(), trace2.Properties["custom.longAttribute"]); + + Assert.True(trace2.Properties.ContainsKey("custom.boolAttribute")); + Assert.Equal(bool.TrueString, trace2.Properties["custom.boolAttribute"]); + } + + /* + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithAnnotationsAndNode() + { + // ARRANGE + var now = DateTime.UtcNow; + + var span = this.CreateBasicSpan(SpanKind.Server, "spanName"); + span.TimeEvents = new Span.Types.TimeEvents + { + TimeEvent = + { + new Span.Types.TimeEvent + { + Time = now.ToTimestamp(), + Annotation = new Span.Types.TimeEvent.Types.Annotation + { + Description = new TruncatableString {Value = "test message1"}, + }, + }, + new Span.Types.TimeEvent + { + Time = now.ToTimestamp(), + MessageEvent = new Span.Types.TimeEvent.Types.MessageEvent + { + Id = 1, + CompressedSize = 2, + UncompressedSize = 3, + Type = Span.Types.TimeEvent.Types.MessageEvent.Types.Type.Received, + }, + }, + }, + }; + + + string hostName = "host", serviceName = "tests", version = "1.2.3.4.5"; + uint pid = 12345; + var lang = LibraryInfo.Types.Language.CSharp; + + var node = CreateBasicNode(hostName, pid, lang, version, serviceName); + node.Attributes.Add("a", "b"); + + // ACT + var sentItems = this.ConvertSpan(span, node, string.Empty); + + // ASSERT + Assert.Equal(3, sentItems.Count); + Assert.Single(sentItems.OfType()); + Assert.Equal(2, sentItems.OfType().Count()); + + var request = sentItems.OfType().Single(); + var trace1 = sentItems.OfType().First(); + var trace2 = sentItems.OfType().Last(); + Assert.Equal(serviceName, request.Context.Cloud.RoleName); + Assert.Equal(serviceName, trace1.Context.Cloud.RoleName); + Assert.Equal(serviceName, trace2.Context.Cloud.RoleName); + + Assert.Equal($"{hostName}.{pid}", request.Context.Cloud.RoleInstance); + Assert.Equal($"{hostName}.{pid}", trace1.Context.Cloud.RoleInstance); + Assert.Equal($"{hostName}.{pid}", trace2.Context.Cloud.RoleInstance); + + Assert.Equal(0, request.Properties.Count); + Assert.Equal(0, trace1.Properties.Count); + Assert.Equal(0, trace2.Properties.Count); + } + + */ + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependenciesWithAnnotations() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + nowDateTimeOffset = nowDateTimeOffset.Subtract(TimeSpan.FromSeconds(1)); + name = "spanName"; + kind = SpanKind.Client; + + annotations = TimedEvents.Create( + new List>() { + TimedEvent.Create(NowTimestamp, Annotation.FromDescription("test message1")), + TimedEvent.Create(null, Annotation.FromDescriptionAndAttributes("test message2", new Dictionary() + { + { "custom.stringAttribute", AttributeValue.StringAttributeValue("string") }, + { "custom.longAttribute", AttributeValue.LongAttributeValue(long.MaxValue) }, + { "custom.boolAttribute", AttributeValue.BooleanAttributeValue(true) }, + })), + }, + droppedEventsCount: 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.Equal(3, sentItems.Count); + Assert.Single(sentItems.OfType()); + Assert.Equal(2, sentItems.OfType().Count()); + + var dependency = sentItems.OfType().Single(); + var trace1 = sentItems.OfType().First(); + var trace2 = sentItems.OfType().Last(); + + Assert.Equal(dependency.Context.Operation.Id, trace1.Context.Operation.Id); + Assert.Equal(dependency.Context.Operation.Id, trace2.Context.Operation.Id); + Assert.Equal(dependency.Id, trace1.Context.Operation.ParentId); + Assert.Equal(dependency.Id, trace2.Context.Operation.ParentId); + + Assert.Equal("test message1", trace1.Message); + Assert.Equal("test message2", trace2.Message); + + Assert.Equal(nowDateTimeOffset, trace1.Timestamp); + Assert.NotEqual(nowDateTimeOffset, trace2.Timestamp); + Assert.True(Math.Abs((DateTime.UtcNow - trace2.Timestamp).TotalSeconds) < 1); + + Assert.False(trace1.Properties.Any()); + Assert.Equal(3, trace2.Properties.Count); + Assert.True(trace2.Properties.ContainsKey("custom.stringAttribute")); + Assert.Equal("string", trace2.Properties["custom.stringAttribute"]); + + Assert.True(trace2.Properties.ContainsKey("custom.longAttribute")); + Assert.Equal(long.MaxValue.ToString(), trace2.Properties["custom.longAttribute"]); + + Assert.True(trace2.Properties.ContainsKey("custom.boolAttribute")); + Assert.Equal(bool.TrueString, trace2.Properties["custom.boolAttribute"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithMessage() + { + this.GetDefaults(out var context, out var parentSpanId, out var hasRemoteParent, out var name, out var startTimestamp, out var attributes, out var annotations, out var messageOrNetworkEvents, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + Thread.Sleep(TimeSpan.FromTicks(10)); + name = "spanName"; + kind = SpanKind.Server; + + messageOrNetworkEvents = TimedEvents.Create( + new List>() + { + TimedEvent.Create(NowTimestamp, MessageEvent.Builder(MessageEventType.Received, 1).SetCompressedMessageSize(2).SetUncompressedMessageSize(3).Build()), + TimedEvent.Create(NowTimestamp, MessageEvent.Builder(MessageEventType.Sent, 4).SetCompressedMessageSize(5).SetUncompressedMessageSize(6).Build()), + TimedEvent.Create(null, MessageEvent.Builder(MessageEventType.Unspecified, 7).SetCompressedMessageSize(8).SetUncompressedMessageSize(9).Build()), + }, + droppedEventsCount: 0); + + var span = SpanData.Create(context, parentSpanId, hasRemoteParent, name, startTimestamp, attributes, annotations, messageOrNetworkEvents, links, childSpanCount, status, kind, endTimestamp); + + var sentItems = this.ConvertSpan(span); + + Assert.Equal(4, sentItems.Count); + Assert.Single(sentItems.OfType()); + Assert.Equal(3, sentItems.OfType().Count()); + + var request = sentItems.OfType().Single(); + var traces = sentItems.OfType().ToArray(); + + foreach (var t in traces) + { + Assert.Equal(request.Context.Operation.Id, t.Context.Operation.Id); + Assert.Equal(request.Id, t.Context.Operation.ParentId); + Assert.False(t.Properties.Any()); + } + + Assert.Equal("MessageEvent. messageId: '1', type: 'Received', compressed size: '2', uncompressed size: '3'", traces[0].Message); + Assert.Equal("MessageEvent. messageId: '4', type: 'Sent', compressed size: '5', uncompressed size: '6'", traces[1].Message); + Assert.Equal("MessageEvent. messageId: '7', type: 'Unspecified', compressed size: '8', uncompressed size: '9'", traces[2].Message); + } + + /* + [Fact] + public void OpenCensusTelemetryConverterTests_TracksRequestWithCorrectIkey() + { + // ARRANGE + var span = this.CreateBasicSpan(SpanKind.Server, "HttpIn"); + + // ACT + var sentItems = this.ConvertSpan(span, null, "ikey1"); + + // ASSERT + var request = sentItems.OfType().Single(); + Assert.Equal("ikey1", request.Context.InstrumentationKey); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksDependencyWithCorrectIkey() + { + // ARRANGE + var span = this.CreateBasicSpan(SpanKind.Client, "HttpOut"); + + // ACT + var sentItems = this.ConvertSpan(span, null, "ikey1"); + + // ASSERT + var dependency = sentItems.OfType().Single(); + Assert.Equal("ikey1", dependency.Context.InstrumentationKey); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksTraceWithCorrectIkey() + { + // ARRANGE + var now = DateTime.UtcNow; + var span = this.CreateBasicSpan(SpanKind.Server, "spanName"); + span.TimeEvents = new Span.Types.TimeEvents + { + TimeEvent = + { + new Span.Types.TimeEvent + { + Time = now.ToTimestamp(), + Annotation = new Span.Types.TimeEvent.Types.Annotation + { + Description = new TruncatableString {Value = "test message1"}, + }, + }, + new Span.Types.TimeEvent + { + Annotation = new Span.Types.TimeEvent.Types.Annotation + { + Description = new TruncatableString {Value = "test message2"}, + Attributes = new Span.Types.Attributes + { + AttributeMap = + { + ["custom.stringAttribute"] = this.CreateAttributeValue("string"), + ["custom.longAttribute"] = this.CreateAttributeValue(long.MaxValue), + ["custom.boolAttribute"] = this.CreateAttributeValue(true), + }, + }, + }, + }, + }, + }; + + // ACT + var sentItems = this.ConvertSpan(span, null, "ikey1"); + + // ASSERT + var request = sentItems.OfType().Single(); + var trace1 = sentItems.OfType().First(); + var trace2 = sentItems.OfType().Last(); + + Assert.Equal("ikey1", request.Context.InstrumentationKey); + Assert.Equal("ikey1", trace1.Context.InstrumentationKey); + Assert.Equal("ikey1", trace2.Context.InstrumentationKey); + } + + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksNodeInfo() + { + // ARRANGE + var start = DateTime.UtcNow; + string hostName = "host", serviceName = "tests", + version = "1.2.3.4.5", eventName = "Config", peer = "1.2.3.4:51639"; + + uint pid = 12345; + var lang = LibraryInfo.Types.Language.CSharp; + + var node = CreateBasicNode(hostName, pid, lang, version, serviceName); + node.Identifier.StartTimestamp = start.ToTimestamp(); + node.Attributes.Add("a", "b"); + + // ACT + this.client.TrackNodeEvent(node, eventName, peer, "ikey1"); + + // ASSERT + Assert.Single(sentItems); + Assert.IsInstanceOfType(sentItems.Single(), typeof(EventTelemetry)); + var evnt = sentItems.OfType().Single(); + Assert.Equal("ikey1", evnt.Context.InstrumentationKey); + Assert.Equal($"{eventName}.node", evnt.Name); + Assert.Equal($"lf_{lang.ToString().ToLower()}-oc:{version}", evnt.Context.GetInternalContext().SdkVersion); + Assert.Equal(serviceName, evnt.Context.Cloud.RoleName); + Assert.Equal($"{hostName}.{pid}", evnt.Context.Cloud.RoleInstance); + + Assert.Equal(GetAssemblyVersionString(), evnt.Properties["lf_version"]); + Assert.Equal(start.ToString("o"), evnt.Properties["process_start_ts"]); + Assert.Equal(peer, evnt.Properties["peer"]); + + Assert.Equal("b", evnt.Properties["a"]); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksEmptyNodeInfo() + { + // ARRANGE + string eventName = "Config", peer = "1.2.3.4:51639"; + var node = new Node(); + + // ACT + this.client.TrackNodeEvent(node, eventName, peer, "ikey1"); + + // ASSERT + Assert.Single(sentItems); + Assert.IsInstanceOfType(sentItems.Single(), typeof(EventTelemetry)); + var evnt = sentItems.OfType().Single(); + Assert.Equal("ikey1", evnt.Context.InstrumentationKey); + Assert.Equal($"{eventName}.node", evnt.Name); + Assert.Equal("lf_unspecified-oc:0.0.0", evnt.Context.GetInternalContext().SdkVersion); + Assert.Null(evnt.Context.Cloud.RoleName); + Assert.Equal(peer, evnt.Properties["peer"]); + Assert.Equal(1, evnt.Properties.Count); + } + + [Fact] + public void OpenCensusTelemetryConverterTests_TracksHalfEmptyNodeInfo() + { + // ARRANGE + string eventName = "Config", peer = "1.2.3.4:51639"; + var node = new Node + { + Identifier = new ProcessIdentifier { Pid = 1 }, + LibraryInfo = new LibraryInfo { ExporterVersion = "1", CoreLibraryVersion = "2" }, + }; + + // ACT + this.client.TrackNodeEvent(node, eventName, peer, "ikey1"); + + // ASSERT + Assert.Single(sentItems); + Assert.True(sentItems.Single() is EventTelemetry); + var evnt = sentItems.OfType().Single(); + Assert.Equal("ikey1", evnt.Context.InstrumentationKey); + Assert.Equal($"{eventName}.node", evnt.Name); + //Assert.Equal("lf_unspecified-oc:2", evnt.Context.GetInternalContext().SdkVersion); + Assert.Null(evnt.Context.Cloud.RoleName); + Assert.Equal(peer, evnt.Properties["peer"]); + Assert.Equal("1", evnt.Properties["oc_exporter_version"]); + Assert.Equal(GetAssemblyVersionString(), evnt.Properties["lf_version"]); + Assert.Equal(3, evnt.Properties.Count); + + Assert.Equal(".1", evnt.Context.Cloud.RoleInstance); + } + + private Node CreateBasicNode(string hostName, uint pid, LibraryInfo.Types.Language lang, string version, string serviceName) + { + return new Node + { + Identifier = new ProcessIdentifier + { + HostName = hostName, + Pid = pid, + }, + LibraryInfo = new LibraryInfo + { + Language = lang, + CoreLibraryVersion = version, + }, + ServiceInfo = new ServiceInfo + { + Name = serviceName, + }, + }; + } + + */ + + private static (string, byte[]) GenerateRandomId(int byteCount) + { + var idBytes = new byte[byteCount]; + Rand.NextBytes(idBytes); + + var idString = BitConverter.ToString(idBytes).Replace("-", "").ToLower(); + + return (idString, idBytes); + } + + private static readonly Random Rand = new Random(); + + /* + internal static string GetAssemblyVersionString() + { + // Since dependencySource is no longer set, sdk version is prepended + // with information which can identify whether RDD was collected by profiler/framework + // For directly using TrackDependency(), version will be simply what is set by core + Type converterType = typeof(OpenCensusTelemetryConverterTests); + + object[] assemblyCustomAttributes = converterType.Assembly.GetCustomAttributes(false); + string versionStr = assemblyCustomAttributes + .OfType() + .First() + .Version; + + Version version = new Version(versionStr); + + string postfix = version.Revision.ToString(CultureInfo.InvariantCulture); + return version.ToString(3) + "-" + postfix; + } + */ + + private void GetDefaults( + out ISpanContext context, + out ISpanId parentSpanId, + out bool? hasRemoteParent, + out string name, + out Timestamp startTimestamp, + out IAttributes attributes, + out ITimedEvents annotations, + out ITimedEvents messageOrNetworkEvents, + out ILinks links, + out int? childSpanCount, + out Status status, + out SpanKind kind, + out Timestamp endTimestamp) + { + context = SpanContext.Create(TraceId.FromBytes(this.testTraceIdBytes), SpanId.FromBytes(this.testSpanIdBytes), TraceOptions.Default, Tracestate.Empty); + parentSpanId = SpanId.Invalid; + hasRemoteParent = null; + name = "spanName"; + startTimestamp = NowTimestamp.AddDuration(Duration.Create(TimeSpan.FromSeconds(-1))); + attributes = null; + annotations = null; + messageOrNetworkEvents = null; + links = null; + childSpanCount = null; + status = null; + kind = SpanKind.Server; + endTimestamp = NowTimestamp; + } + } +}; \ No newline at end of file diff --git a/test/OpenCensus.Exporter.ApplicationInsights.Tests/OpenCensus.Exporter.ApplicationInsights.Tests.csproj b/test/OpenCensus.Exporter.ApplicationInsights.Tests/OpenCensus.Exporter.ApplicationInsights.Tests.csproj new file mode 100644 index 000000000..f79e7715b --- /dev/null +++ b/test/OpenCensus.Exporter.ApplicationInsights.Tests/OpenCensus.Exporter.ApplicationInsights.Tests.csproj @@ -0,0 +1,44 @@ + + + + + Unit test project for ApplicationInsights Exporter for OpenCensus + net46;netcoreapp2.0 + netcoreapp2.0 + + + + + PreserveNewest + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + full + true + + + \ No newline at end of file diff --git a/test/OpenCensus.Exporter.ApplicationInsights.Tests/xunit.runner.json b/test/OpenCensus.Exporter.ApplicationInsights.Tests/xunit.runner.json new file mode 100644 index 000000000..4256da35b --- /dev/null +++ b/test/OpenCensus.Exporter.ApplicationInsights.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "parallelizeTestCollections": true +} \ No newline at end of file diff --git a/test/OpenCensus.Exporter.Stackdriver.Tests/Impl/StackdriverStatsConfigurationTests.cs b/test/OpenCensus.Exporter.Stackdriver.Tests/Impl/StackdriverStatsConfigurationTests.cs new file mode 100644 index 000000000..d148a401a --- /dev/null +++ b/test/OpenCensus.Exporter.Stackdriver.Tests/Impl/StackdriverStatsConfigurationTests.cs @@ -0,0 +1,68 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Exporter.Stackriver.Tests +{ + using OpenCensus.Exporter.Stackdriver.Implementation; + using System; + using Xunit; + + public class StackdriverStatsConfigurationTests + { + public StackdriverStatsConfigurationTests() + { + // Setting this for unit testing purposes, so we don't need credentials for real Google Cloud Account + Environment.SetEnvironmentVariable("GOOGLE_PROJECT_ID", "test", EnvironmentVariableTarget.Process); + } + + [Fact] + public void StatsConfiguration_ByDefault_MetricNamePrefixEmpty() + { + Assert.NotNull(StackdriverStatsConfiguration.Default); + Assert.Equal(GoogleCloudResourceUtils.GetProjectId(), StackdriverStatsConfiguration.Default.ProjectId); + Assert.Equal(string.Empty, StackdriverStatsConfiguration.Default.MetricNamePrefix); + } + + [Fact] + public void StatsConfiguration_ByDeafult_ProjectIdIsGoogleCloudProjectId() + { + Assert.NotNull(StackdriverStatsConfiguration.Default); + Assert.Equal(GoogleCloudResourceUtils.GetProjectId(), StackdriverStatsConfiguration.Default.ProjectId); + } + + [Fact] + public void StatsConfiguration_ByDefault_ExportIntervalMinute() + { + Assert.Equal(TimeSpan.FromMinutes(1), StackdriverStatsConfiguration.Default.ExportInterval); + } + + [Fact] + public void StatsConfiguration_ByDefault_MonitoredResourceIsGlobal() + { + Assert.NotNull(StackdriverStatsConfiguration.Default.MonitoredResource); + + Assert.Equal(Constants.GLOBAL, StackdriverStatsConfiguration.Default.MonitoredResource.Type); + + Assert.NotNull(StackdriverStatsConfiguration.Default.MonitoredResource.Labels); + + Assert.True(StackdriverStatsConfiguration.Default.MonitoredResource.Labels.ContainsKey("project_id")); + Assert.True(StackdriverStatsConfiguration.Default.MonitoredResource.Labels.ContainsKey(Constants.PROJECT_ID_LABEL_KEY)); + Assert.Equal( + StackdriverStatsConfiguration.Default.ProjectId, + StackdriverStatsConfiguration.Default.MonitoredResource.Labels[Constants.PROJECT_ID_LABEL_KEY]); + } + } +} \ No newline at end of file diff --git a/test/OpenCensus.Exporter.Stackdriver.Tests/OpenCensus.Exporter.Stackdriver.Tests.csproj b/test/OpenCensus.Exporter.Stackdriver.Tests/OpenCensus.Exporter.Stackdriver.Tests.csproj new file mode 100644 index 000000000..3c366f7aa --- /dev/null +++ b/test/OpenCensus.Exporter.Stackdriver.Tests/OpenCensus.Exporter.Stackdriver.Tests.csproj @@ -0,0 +1,50 @@ + + + + + Unit test project for Stackdriver Exporter for OpenCensus + net46;netcoreapp2.0 + netcoreapp2.0 + OpenCensus.Exporter.Stackdriver.Tests + OpenCensus.Exporter.Stackdriver.Tests + OpenCensus;Tracing;Management;Monitoring;Stackdriver;Google;Cloud + http://opencensus.io + Apache-2.0 + OpenCensus + + + + + PreserveNewest + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + All + + + + + + + + + + full + true + + + \ No newline at end of file diff --git a/test/OpenCensus.Exporter.Stackdriver.Tests/xunit.runner.json b/test/OpenCensus.Exporter.Stackdriver.Tests/xunit.runner.json new file mode 100644 index 000000000..9fbc90115 --- /dev/null +++ b/test/OpenCensus.Exporter.Stackdriver.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "maxParallelThreads": 1, + "parallelizeTestCollections": false +} \ No newline at end of file diff --git a/test/OpenCensus.Tests/Impl/Common/DurationTest.cs b/test/OpenCensus.Tests/Impl/Common/DurationTest.cs new file mode 100644 index 000000000..29de879e2 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Common/DurationTest.cs @@ -0,0 +1,76 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Common.Test +{ + using Xunit; + + public class DurationTest + { + [Fact] + public void TestDurationCreate() + { + Assert.Equal(24, Duration.Create(24, 42).Seconds); + Assert.Equal(42, Duration.Create(24, 42).Nanos); + Assert.Equal(-24, Duration.Create(-24, -42).Seconds); + Assert.Equal(-42, Duration.Create(-24, -42).Nanos); + Assert.Equal(315576000000L, Duration.Create(315576000000L, 999999999).Seconds); + Assert.Equal(999999999, Duration.Create(315576000000L, 999999999).Nanos); + Assert.Equal(-315576000000L, Duration.Create(-315576000000L, -999999999).Seconds); + Assert.Equal(-999999999, Duration.Create(-315576000000L, -999999999).Nanos); + } + + [Fact] + public void TestDurationCreateInvalidInput() + { + Assert.Equal(Duration.Create(0, 0), Duration.Create(-315576000001L, 0)); + Assert.Equal(Duration.Create(0, 0), Duration.Create(315576000001L, 0)); + Assert.Equal(Duration.Create(0, 0), Duration.Create(0, 1000000000)); + Assert.Equal(Duration.Create(0, 0), Duration.Create(0, -1000000000)); + Assert.Equal(Duration.Create(0, 0), Duration.Create(-1, 1)); + Assert.Equal(Duration.Create(0, 0), Duration.Create(1, -1)); + } + + [Fact] + public void Duration_CompareLength() + { + Assert.Equal(0, Duration.Create(0, 0).CompareTo(Duration.Create(0, 0))); + Assert.Equal(0, Duration.Create(24, 42).CompareTo(Duration.Create(24, 42))); + Assert.Equal(0, Duration.Create(-24, -42).CompareTo(Duration.Create(-24, -42))); + Assert.Equal(1, Duration.Create(25, 42).CompareTo(Duration.Create(24, 42))); + Assert.Equal(1, Duration.Create(24, 45).CompareTo(Duration.Create(24, 42))); + Assert.Equal(-1, Duration.Create(24, 42).CompareTo(Duration.Create(25, 42))); + Assert.Equal(-1, Duration.Create(24, 42).CompareTo(Duration.Create(24, 45))); + Assert.Equal(-1, Duration.Create(-24, -45).CompareTo(Duration.Create(-24, -42))); + Assert.Equal(1, Duration.Create(-24, -42).CompareTo(Duration.Create(-25, -42))); + Assert.Equal(1, Duration.Create(24, 42).CompareTo(Duration.Create(-24, -42))); + } + + [Fact] + public void TestDurationEqual() + { + // Positive tests. + Assert.Equal(Duration.Create(0, 0), Duration.Create(0, 0)); + Assert.Equal(Duration.Create(24, 42), Duration.Create(24, 42)); + Assert.Equal(Duration.Create(-24, -42), Duration.Create(-24, -42)); + // Negative tests. + Assert.NotEqual(Duration.Create(24, 42), Duration.Create(25, 42)); + Assert.NotEqual(Duration.Create(24, 42), Duration.Create(24, 43)); + Assert.NotEqual(Duration.Create(-24, -42), Duration.Create(-25, -42)); + Assert.NotEqual(Duration.Create(-24, -42), Duration.Create(-24, -43)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Common/TimestampTest.cs b/test/OpenCensus.Tests/Impl/Common/TimestampTest.cs new file mode 100644 index 000000000..4c874393a --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Common/TimestampTest.cs @@ -0,0 +1,161 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Common.Test +{ + using System; + using Xunit; + + public class TimestampTest + { + [Fact] + public void TimestampCreate() + { + Assert.Equal(24, Timestamp.Create(24, 42).Seconds); + Assert.Equal(42, Timestamp.Create(24, 42).Nanos); + Assert.Equal(-24, Timestamp.Create(-24, 42).Seconds); + Assert.Equal(42, Timestamp.Create(-24, 42).Nanos); + Assert.Equal(315576000000L, Timestamp.Create(315576000000L, 999999999).Seconds); + Assert.Equal(999999999, Timestamp.Create(315576000000L, 999999999).Nanos); + Assert.Equal(-315576000000L, Timestamp.Create(-315576000000L, 999999999).Seconds); + Assert.Equal(999999999, Timestamp.Create(-315576000000L, 999999999).Nanos); + } + + [Fact] + public void TimestampCreate_InvalidInput() + { + Assert.Equal(Timestamp.Create(0, 0), Timestamp.Create(-315576000001L, 0)); + Assert.Equal(Timestamp.Create(0, 0), Timestamp.Create(315576000001L, 0)); + Assert.Equal(Timestamp.Create(0, 0), Timestamp.Create(1, 1000000000)); + Assert.Equal(Timestamp.Create(0, 0), Timestamp.Create(1, -1)); + Assert.Equal(Timestamp.Create(0, 0), Timestamp.Create(-1, 1000000000)); + Assert.Equal(Timestamp.Create(0, 0), Timestamp.Create(-1, -1)); + } + + [Fact] + public void TimestampFromMillis() + { + Assert.Equal(Timestamp.Create(0, 0), Timestamp.FromMillis(0)); + Assert.Equal(Timestamp.Create(0, 987000000), Timestamp.FromMillis(987)); + Assert.Equal(Timestamp.Create(3, 456000000), Timestamp.FromMillis(3456)); + } + + [Fact] + public void TimestampFromMillis_Negative() + { + Assert.Equal(Timestamp.Create(-1, 999000000), Timestamp.FromMillis(-1)); + Assert.Equal(Timestamp.Create(-1, 1000000), Timestamp.FromMillis(-999)); + Assert.Equal(Timestamp.Create(-4, 544000000), Timestamp.FromMillis(-3456)); + } + + [Fact] + public void TimestampAddNanos() + { + Timestamp timestamp = Timestamp.Create(1234, 223); + Assert.Equal(timestamp, timestamp.AddNanos(0)); + Assert.Equal(Timestamp.Create(1235, 0), timestamp.AddNanos(999999777)); + Assert.Equal(Timestamp.Create(1235, 300200723), timestamp.AddNanos(1300200500)); + Assert.Equal(Timestamp.Create(1236, 0), timestamp.AddNanos(1999999777)); + Assert.Equal(Timestamp.Create(1243, 876544012), timestamp.AddNanos(9876543789L)); + Assert.Equal(Timestamp.Create(1234L + 9223372036L, 223 + 854775807), timestamp.AddNanos(Int64.MaxValue)) + ; + } + + [Fact] + public void TimestampAddNanos_Negative() + { + Timestamp timestamp = Timestamp.Create(1234, 223); + Assert.Equal(Timestamp.Create(1234, 0), timestamp.AddNanos(-223)); + Assert.Equal(Timestamp.Create(1233, 0), timestamp.AddNanos(-1000000223)); + Assert.Equal(Timestamp.Create(1232, 699799723), timestamp.AddNanos(-1300200500)); + Assert.Equal(Timestamp.Create(1229, 876544010), timestamp.AddNanos(-4123456213L)); + Assert.Equal(Timestamp.Create(1234L - 9223372036L - 1, 223 + 145224192), timestamp.AddNanos(Int64.MinValue)) + ; + } + + [Fact] + public void TimestampAddDuration() + { + Timestamp timestamp = Timestamp.Create(1234, 223); + Assert.Equal(Timestamp.Create(1235, 223), timestamp.AddDuration(Duration.Create(1, 0))); + Assert.Equal(Timestamp.Create(1234, 224), timestamp.AddDuration(Duration.Create(0, 1))); + Assert.Equal(Timestamp.Create(1235, 224), timestamp.AddDuration(Duration.Create(1, 1))); + Assert.Equal(Timestamp.Create(1236, 123), timestamp.AddDuration(Duration.Create(1, 999999900))); + } + + [Fact] + public void TimestampAddDuration_Negative() + { + Timestamp timestamp = Timestamp.Create(1234, 223); + Assert.Equal(Timestamp.Create(0, 0), timestamp.AddDuration(Duration.Create(-1234, -223))); + Assert.Equal(Timestamp.Create(1233, 223), timestamp.AddDuration(Duration.Create(-1, 0))); + Assert.Equal(Timestamp.Create(1233, 222), timestamp.AddDuration(Duration.Create(-1, -1))); + Assert.Equal(Timestamp.Create(1232, 999999900), timestamp.AddDuration(Duration.Create(-1, -323))); + Assert.Equal(Timestamp.Create(1200, 224), timestamp.AddDuration(Duration.Create(-33, -999999999))); + } + + [Fact] + public void TimestampSubtractTimestamp() + { + Timestamp timestamp = Timestamp.Create(1234, 223); + Assert.Equal(Duration.Create(1234, 223), timestamp.SubtractTimestamp(Timestamp.Create(0, 0))); + Assert.Equal(Duration.Create(1, 0), timestamp.SubtractTimestamp(Timestamp.Create(1233, 223))); + Assert.Equal(Duration.Create(1, 1), timestamp.SubtractTimestamp(Timestamp.Create(1233, 222))); + Assert.Equal(Duration.Create(1, 323), timestamp.SubtractTimestamp(Timestamp.Create(1232, 999999900))); + Assert.Equal(Duration.Create(33, 999999999), timestamp.SubtractTimestamp(Timestamp.Create(1200, 224))); + } + + [Fact] + public void TimestampSubtractTimestamp_NegativeResult() + { + Timestamp timestamp = Timestamp.Create(1234, 223); + Assert.Equal(Duration.Create(-1, 0), timestamp.SubtractTimestamp(Timestamp.Create(1235, 223))); + Assert.Equal(Duration.Create(0, -1), timestamp.SubtractTimestamp(Timestamp.Create(1234, 224))); + Assert.Equal(Duration.Create(-1, -1), timestamp.SubtractTimestamp(Timestamp.Create(1235, 224))); + Assert.Equal(Duration.Create(-1, -999999900), timestamp.SubtractTimestamp(Timestamp.Create(1236, 123))); + } + + [Fact] + public void Timestamp_CompareTo() + { + Assert.Equal(0, Timestamp.Create(0, 0).CompareTo(Timestamp.Create(0, 0))); + Assert.Equal(0, Timestamp.Create(24, 42).CompareTo(Timestamp.Create(24, 42))); + Assert.Equal(0, Timestamp.Create(-24, 42).CompareTo(Timestamp.Create(-24, 42))); + Assert.Equal(1, Timestamp.Create(25, 42).CompareTo(Timestamp.Create(24, 42))); + Assert.Equal(1, Timestamp.Create(24, 45).CompareTo(Timestamp.Create(24, 42))); + Assert.Equal(-1, Timestamp.Create(24, 42).CompareTo(Timestamp.Create(25, 42))); + Assert.Equal(-1, Timestamp.Create(24, 42).CompareTo(Timestamp.Create(24, 45))); + Assert.Equal(-1, Timestamp.Create(-25, 42).CompareTo(Timestamp.Create(-24, 42))); + Assert.Equal(1, Timestamp.Create(-24, 45).CompareTo(Timestamp.Create(-24, 42))); + Assert.Equal(1, Timestamp.Create(-24, 42).CompareTo(Timestamp.Create(-25, 42))); + Assert.Equal(-1, Timestamp.Create(-24, 42).CompareTo(Timestamp.Create(-24, 45))); + } + + [Fact] + public void Timestamp_Equal() + { + // Positive tests. + Assert.Equal(Timestamp.Create(0, 0), Timestamp.Create(0, 0)); + Assert.Equal(Timestamp.Create(24, 42), Timestamp.Create(24, 42)); + Assert.Equal(Timestamp.Create(-24, 42), Timestamp.Create(-24, 42)); + // Negative tests. + Assert.NotEqual(Timestamp.Create(24, 42), Timestamp.Create(25, 42)); + Assert.NotEqual(Timestamp.Create(24, 42), Timestamp.Create(24, 43)); + Assert.NotEqual(Timestamp.Create(-24, 42), Timestamp.Create(-25, 42)); + Assert.NotEqual(Timestamp.Create(-24, 42), Timestamp.Create(-24, 43)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Internal/TimestampConverterTest.cs b/test/OpenCensus.Tests/Impl/Internal/TimestampConverterTest.cs new file mode 100644 index 000000000..416a889d4 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Internal/TimestampConverterTest.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Internal.Test +{ + using Xunit; + + public class TimestampConverterTest + { + [Fact] + public void SimpleTest() + { + Assert.True(true); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Resources/ResourceTest.cs b/test/OpenCensus.Tests/Impl/Resources/ResourceTest.cs new file mode 100644 index 000000000..8c8c6602d --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Resources/ResourceTest.cs @@ -0,0 +1,173 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Impl.Resources +{ + using Xunit; + using OpenCensus.Resources; + using OpenCensus.Tags; + using OpenCensus.Implementation; + + public class ResourceTest + { + [Fact] + public void TryParseResourceType_NullOrEmptyType_GlobalResourceSet() + { + // Arrange + string rawResourceType = string.Empty; + string resourceType; + + // Act (empty) + var parsed = Resource.TryParseResourceType(rawResourceType, out resourceType); + + // Assert (empty) + Assert.False(parsed); + Assert.Equal(Constants.GlobalResourceType, resourceType); + + // Act (null) + parsed = Resource.TryParseResourceType(rawResourceType, out resourceType); + + // Assert (null) + Assert.False(parsed); + Assert.Equal(Constants.GlobalResourceType, resourceType); + } + + [Fact] + public void TryParseResourceType_LongResourceTypeName_GlobalResourceSet() + { + // Arrange + string longResouceTypeName = "a".PadLeft(Constants.MaxResourceTypeNameLength + 1, 'a'); + string resourceType; + + // Act + var parsed = Resource.TryParseResourceType(longResouceTypeName, out resourceType); + + // Assert + Assert.False(parsed); + Assert.Equal(Constants.GlobalResourceType, resourceType); + } + + [Fact] + public void TryParseResourceType_NameWithSpaces_SpacesTrimmed() + { + // Arrange + string rawResouceType = " a "; + string resourceType; + + // Act + var parsed = Resource.TryParseResourceType(rawResouceType, out resourceType); + + // Assert + Assert.True(parsed); + Assert.Equal("a", resourceType); + } + + [Fact] + public void ParseResourceLabels_WrongKeyValueDelimiter_PairIgnored() + { + // Arrange + string resourceLabels = "k1:v1,k2=v2"; + + // Act + var tags = Resource.ParseResourceLabels(resourceLabels); + + // Assert + Assert.NotNull(tags); + Assert.NotEmpty(tags); + Assert.Single(tags); + Assert.Equal(TagKey.Create("k2"), tags[0].Key); + Assert.Equal(TagValue.Create("v2"), tags[0].Value); + } + + [Fact] + public void ParseResourceLabels_LongValueName_AllLaterLabelsIgnored() + { + // Arrange + string longValue = "a".PadLeft(Constants.MaxResourceTypeNameLength + 1, 'a'); + string resourceLabels = $"k1={longValue};k2=v2"; + + // Act + var tags = Resource.ParseResourceLabels(resourceLabels); + + // Assert + Assert.NotNull(tags); + Assert.Empty(tags); + } + + [Fact] + public void ParseResourceLabels_LongKeyName_AllLaterLabelsIgnored() + { + // Arrange + string longKey = "a".PadLeft(Constants.MaxResourceTypeNameLength + 1, 'a'); + string resourceLabels = $"{longKey}=v1;k2=v2"; + + // Act + var tags = Resource.ParseResourceLabels(resourceLabels); + + // Assert + Assert.NotNull(tags); + Assert.Empty(tags); + } + + [Fact] + public void ParseResourceLabels_ValueWithParenthesis_StrippedValue() + { + // Arrange + string resourceLabels = "k1=\"v1\""; + + // Act + var tags = Resource.ParseResourceLabels(resourceLabels); + + // Assert + Assert.NotNull(tags); + Assert.NotEmpty(tags); + Assert.Equal(TagKey.Create("k1"), tags[0].Key); + Assert.Equal(TagValue.Create("v1"), tags[0].Value); + } + + [Fact] + public void ParseResourceLabels_EmptyString_EmptyMapReturned() + { + // Arrange + string resourceLabels = ""; + + // Act + var tags = Resource.ParseResourceLabels(resourceLabels); + + // Assert + Assert.NotNull(tags); + Assert.Empty(tags); + } + + [Fact] + public void ParseResourceLabels_CommaSeparated_MapReturned() + { + // Arrange + string resourceLabels = "key1=val1,key2=val2"; + + // Act + var tags = Resource.ParseResourceLabels(resourceLabels); + + // Assert + Assert.NotNull(tags); + Assert.Equal(2, tags.Length); + Assert.Equal(TagKey.Create("key1"), tags[0].Key); + Assert.Equal(TagKey.Create("key2"), tags[1].Key); + Assert.Equal(TagValue.Create("val1"), tags[0].Value); + Assert.Equal(TagValue.Create("val2"), tags[1].Value); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/AggregationDataTest.cs b/test/OpenCensus.Tests/Impl/Stats/AggregationDataTest.cs new file mode 100644 index 000000000..1bb31ee03 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/AggregationDataTest.cs @@ -0,0 +1,182 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Stats.Aggregations; + using Xunit; + + public class AggregationDataTest + { + private static readonly double TOLERANCE = 1e-6; + + [Fact] + public void TestCreateDistributionData() + { + IDistributionData distributionData = + DistributionData.Create(7.7, 10, 1.1, 9.9, 32.2, new List() { 4L, 1L, 5L }); + + Assert.InRange(distributionData.Mean, 7.7 - TOLERANCE, 7.7 + TOLERANCE); + Assert.Equal(10, distributionData.Count); + Assert.InRange(distributionData.Min, 1.1 - TOLERANCE, 1.1 + TOLERANCE); + Assert.InRange(distributionData.Max, 9.9 - TOLERANCE, 9.9 + TOLERANCE); + Assert.InRange(distributionData.SumOfSquaredDeviations, 32.2 - TOLERANCE, 32.2 + TOLERANCE); + Assert.Equal(4, distributionData.BucketCounts[0]); + Assert.Equal(1, distributionData.BucketCounts[1]); + Assert.Equal(5, distributionData.BucketCounts[2]); + } + + [Fact] + public void PreventNullBucketCountList() + { + // thrown.expect(NullPointerException.class); + // thrown.expectMessage("bucket counts should not be null."); + Assert.Throws(() => DistributionData.Create(1, 1, 1, 1, 0, null)); + } + + [Fact] + public void PreventMinIsGreaterThanMax() + { + // thrown.expect(IllegalArgumentException.class); + // thrown.expectMessage("max should be greater or equal to min."); + Assert.Throws(() => DistributionData.Create(1, 1, 10, 1, 0, new List() { 0L, 1L, 0L })); + } + + [Fact] + public void TestEquals() + { + var a1 = SumDataDouble.Create(10.0); + var a2 = SumDataDouble.Create(20.0); + var a3 = SumDataLong.Create(20); + var a5 = CountData.Create(40); + var a6 = CountData.Create(80); + var a7 = DistributionData.Create(10, 10, 1, 1, 0, new List() { 0L, 10L, 0L }); + var a8 = DistributionData.Create(10, 10, 1, 1, 0, new List() { 0L, 10L, 100L }); + var a9 = DistributionData.Create(110, 10, 1, 1, 0, new List() { 0L, 10L, 0L }); + var a10 = DistributionData.Create(10, 110, 1, 1, 0, new List() { 0L, 10L, 0L }); + var a11 = DistributionData.Create(10, 10, -1, 1, 0, new List() { 0L, 10L, 0L }); + var a12 = DistributionData.Create(10, 10, 1, 5, 0, new List() { 0L, 10L, 0L }); + var a13 = DistributionData.Create(10, 10, 1, 1, 55.5, new List() { 0L, 10L, 0L }); + var a14 = MeanData.Create(5.0, 1, 5.0, 5.0); + var a15 = MeanData.Create(-5.0, 1, -5.0, -5.0); + var a16 = LastValueDataDouble.Create(20.0); + var a17 = LastValueDataLong.Create(20); + + var a1a = SumDataDouble.Create(10.0); + var a2a = SumDataDouble.Create(20.0); + var a3a = SumDataLong.Create(20); + var a5a = CountData.Create(40); + var a6a = CountData.Create(80); + var a7a = DistributionData.Create(10, 10, 1, 1, 0, new List() { 0L, 10L, 0L }); + var a8a = DistributionData.Create(10, 10, 1, 1, 0, new List() { 0L, 10L, 100L }); + var a9a = DistributionData.Create(110, 10, 1, 1, 0, new List() { 0L, 10L, 0L }); + var a10a = DistributionData.Create(10, 110, 1, 1, 0, new List() { 0L, 10L, 0L }); + var a11a = DistributionData.Create(10, 10, -1, 1, 0, new List() { 0L, 10L, 0L }); + var a12a = DistributionData.Create(10, 10, 1, 5, 0, new List() { 0L, 10L, 0L }); + var a13a = DistributionData.Create(10, 10, 1, 1, 55.5, new List() { 0L, 10L, 0L }); + var a14a = MeanData.Create(5.0, 1, 5.0, 5.0); + var a15a = MeanData.Create(-5.0, 1, -5.0, -5.0); + var a16a = LastValueDataDouble.Create(20.0); + var a17a = LastValueDataLong.Create(20); + + Assert.Equal(a1, a1a); + Assert.Equal(a2, a2a); + Assert.Equal(a3, a3a); + Assert.Equal(a5, a5a); + Assert.Equal(a6, a6a); + Assert.Equal(a7, a7a); + Assert.Equal(a8, a8a); + Assert.Equal(a9, a9a); + Assert.Equal(a10, a10a); + Assert.Equal(a11, a11a); + Assert.Equal(a12, a12a); + Assert.Equal(a13, a13a); + Assert.Equal(a14, a14a); + Assert.Equal(a15, a15a); + Assert.Equal(a16, a16a); + Assert.Equal(a17, a17a); + + } + + [Fact] + public void TestMatchAndGet() + { + List aggregations = + new List() { + SumDataDouble.Create(10.0), + SumDataLong.Create(100000000), + CountData.Create(40), + MeanData.Create(100.0, 10, 300.0, 500.0), + DistributionData.Create(1, 1, 1, 1, 0, new List() { 0L, 10L, 0L }), + LastValueDataDouble.Create(20.0), + LastValueDataLong.Create(200000000L), + }; + + List actual = new List(); + foreach (IAggregationData aggregation in aggregations) + { + aggregation.Match( + (arg) => + { + actual.Add(arg.Sum); + return null; + }, + (arg) => + { + actual.Add(arg.Sum); + return null; + }, + (arg) => + { + actual.Add(arg.Count); + return null; + }, + (arg) => + { + actual.Add(arg.Mean); + return null; + }, + (arg) => + { + actual.Add(arg.BucketCounts); + return null; + }, + (arg) => + { + actual.Add(arg.LastValue); + return null; + }, + (arg) => + { + actual.Add(arg.LastValue); + return null; + }, + (arg) => { throw new ArgumentException(); }); + } + Assert.Equal(10.0, actual[0]); + Assert.Equal(100000000L, actual[1]); + Assert.Equal(40L, actual[2]); + Assert.Equal(100.0, actual[3]); + Assert.Equal(new List() { 0L, 10L, 0L }, actual[4]); + Assert.Equal(20.0, actual[5]); + Assert.Equal(200000000L, actual[6]); + + } + } +} + diff --git a/test/OpenCensus.Tests/Impl/Stats/AggregationTest.cs b/test/OpenCensus.Tests/Impl/Stats/AggregationTest.cs new file mode 100644 index 000000000..9b9e388ee --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/AggregationTest.cs @@ -0,0 +1,114 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Stats.Aggregations; + using Xunit; + + public class AggregationTest + { + [Fact] + public void TestCreateDistribution() + { + IBucketBoundaries bucketBoundaries = BucketBoundaries.Create(new List() { 0.1, 2.2, 33.3 }); + IDistribution distribution = Distribution.Create(bucketBoundaries); + Assert.Equal(bucketBoundaries, distribution.BucketBoundaries); + } + + [Fact] + public void TestNullBucketBoundaries() + { + Assert.Throws(() => Distribution.Create(null)); ; + } + + [Fact] + public void TestEquals() + { + IAggregation a1 = Sum.Create(); + IAggregation a2 = Sum.Create(); + + IAggregation a3 = Count.Create(); + IAggregation a4 = Count.Create(); + + IAggregation a5 = Distribution.Create(BucketBoundaries.Create(new List() { -10.0, 1.0, 5.0 })); + IAggregation a6 = Distribution.Create(BucketBoundaries.Create(new List() { -10.0, 1.0, 5.0 })); + + IAggregation a7 = Distribution.Create(BucketBoundaries.Create(new List() { 0.0, 1.0, 5.0 })); + IAggregation a8 = Distribution.Create(BucketBoundaries.Create(new List() { 0.0, 1.0, 5.0 })); + + IAggregation a9 = Mean.Create(); + IAggregation a10 = Mean.Create(); + + IAggregation a11 = LastValue.Create(); + IAggregation a12 = LastValue.Create(); + + Assert.Equal(a1, a2); + Assert.Equal(a3, a4); + Assert.Equal(a5, a6); + Assert.Equal(a7, a8); + Assert.Equal(a9, a10); + Assert.Equal(a11, a12); + + } + + [Fact] + public void TestMatch() + { + List aggregations = + new List() { + Sum.Create(), + Count.Create(), + Mean.Create(), + Distribution.Create(BucketBoundaries.Create(new List() {-10.0, 1.0, 5.0 })), + LastValue.Create(),}; + List actual = new List(); + foreach (IAggregation aggregation in aggregations) + { + actual.Add( + aggregation.Match( + (arg) => + { + return "SUM"; + }, + (arg) => + { + return "COUNT"; + }, + (arg) => + { + return "MEAN"; + }, + (arg) => + { + return "DISTRIBUTION"; + }, + (arg) => + { + return "LASTVALUE"; + }, + (arg) => + { + throw new ArgumentException(); + })); + + } + Assert.Equal(new List() { "SUM", "COUNT", "MEAN", "DISTRIBUTION", "LASTVALUE" }, actual); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/BucketBoundariesTest.cs b/test/OpenCensus.Tests/Impl/Stats/BucketBoundariesTest.cs new file mode 100644 index 000000000..70628bec9 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/BucketBoundariesTest.cs @@ -0,0 +1,83 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using Xunit; + + public class BucketBoundariesTest + { + + [Fact] + public void TestConstructBoundaries() + { + List buckets = new List() { 0.0, 1.0, 2.0 }; + IBucketBoundaries bucketBoundaries = BucketBoundaries.Create(buckets); + Assert.Equal(buckets, bucketBoundaries.Boundaries); + } + + [Fact] + public void TestBoundariesDoesNotChangeWithOriginalList() + { + List original = new List(); + original.Add(0.0); + original.Add(1.0); + original.Add(2.0); + IBucketBoundaries bucketBoundaries = BucketBoundaries.Create(original); + original[2] = 3.0; + original.Add(4.0); + List expected = new List() { 0.0, 1.0, 2.0 }; + Assert.NotEqual(original, bucketBoundaries.Boundaries); + Assert.Equal(expected, bucketBoundaries.Boundaries); + } + + [Fact] + public void TestNullBoundaries() + { + Assert.Throws(() => BucketBoundaries.Create(null)); + } + + [Fact] + public void TestUnsortedBoundaries() + { + List buckets = new List() { 0.0, 1.0, 1.0 }; + Assert.Throws(() => BucketBoundaries.Create(buckets)); + } + + [Fact] + public void TestNoBoundaries() + { + List buckets = new List(); + IBucketBoundaries bucketBoundaries = BucketBoundaries.Create(buckets); + Assert.Equal(buckets, bucketBoundaries.Boundaries); + } + + [Fact] + public void TestBucketBoundariesEquals() + { + var b1 = BucketBoundaries.Create(new List() { -1.0, 2.0 }); + var b2 = BucketBoundaries.Create(new List() { -1.0, 2.0 }); + var b3 = BucketBoundaries.Create(new List() { -1.0 }); + Assert.Equal(b1, b2); + Assert.Equal(b3, b3); + Assert.NotEqual(b1, b3); + Assert.NotEqual(b2, b3); + + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/CurrentStatsStateTest.cs b/test/OpenCensus.Tests/Impl/Stats/CurrentStatsStateTest.cs new file mode 100644 index 000000000..2f612b8ac --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/CurrentStatsStateTest.cs @@ -0,0 +1,104 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Xunit; + + public class CurrentStatsStateTest + { + [Fact] + public void DefaultState() + { + Assert.Equal(StatsCollectionState.ENABLED, new CurrentStatsState().Value); + } + + [Fact] + public void SetState() + { + CurrentStatsState state = new CurrentStatsState(); + Assert.True(state.Set(StatsCollectionState.DISABLED)); + Assert.Equal(StatsCollectionState.DISABLED, state.Internal); + Assert.True(state.Set(StatsCollectionState.ENABLED)); + Assert.Equal(StatsCollectionState.ENABLED, state.Internal); + Assert.False(state.Set(StatsCollectionState.ENABLED)); + } + + + + [Fact] + public void PreventSettingStateAfterReadingState() + { + CurrentStatsState state = new CurrentStatsState(); + var st = state.Value; + Assert.Throws(() => state.Set(StatsCollectionState.DISABLED)); + } + + [Fact] + + public async Task PreventSettingStateAfterReadingState_IsThreadSafe() + { + // This test relies on timing, and as such may not FAIL reliably under some conditions + // (e.g. more/less machine load, faster/slower processors). + // It will not incorrectly fail transiently though. + + for (int i = 0; i < 10; i ++) + { + var state = new CurrentStatsState(); + + using (var cts = new CancellationTokenSource()) + { + var _ = Task.Run( + async () => + { + while (!cts.IsCancellationRequested) + { + try + { + state.Set(StatsCollectionState.DISABLED); + state.Set(StatsCollectionState.ENABLED); + } + catch + { + // Throw is expected after the read is performed + } + } + }, + cts.Token); + + await Task.Delay(10); + + // Read the value a bunch of times + var values = Enumerable.Range(0, 20) + .Select(__ => state.Value) + .ToList(); + + // They should all be the same + foreach (var item in values) + { + Assert.Equal(item, values[0]); + } + + cts.Cancel(); + } + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/MeasureMapBuilderTest.cs b/test/OpenCensus.Tests/Impl/Stats/MeasureMapBuilderTest.cs new file mode 100644 index 000000000..00de89d39 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/MeasureMapBuilderTest.cs @@ -0,0 +1,153 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Stats.Measurements; + using OpenCensus.Stats.Measures; + using OpenCensus.Utils; + using Xunit; + + public class MeasureMapBuilderTest + { + + private static readonly IMeasureDouble M1 = MakeSimpleMeasureDouble("m1"); + private static readonly IMeasureDouble M2 = MakeSimpleMeasureDouble("m2"); + private static readonly IMeasureLong M3 = MakeSimpleMeasureLong("m3"); + private static readonly IMeasureLong M4 = MakeSimpleMeasureLong("m4"); + + [Fact] + public void TestPutDouble() + { + var metrics = MeasureMapBuilder.Builder().Put(M1, 44.4).Build(); + AssertContains(metrics, MeasurementDouble.Create(M1, 44.4) ); + } + + [Fact] + public void TestPutLong() + { + var metrics = MeasureMapBuilder.Builder().Put(M3, 9999L).Put(M4, 8888L).Build(); + AssertContains(metrics, MeasurementLong.Create(M3, 9999L), MeasurementLong.Create(M4, 8888L)); + } + + [Fact] + public void TestCombination() + { + var metrics = + MeasureMapBuilder.Builder() + .Put(M1, 44.4) + .Put(M2, 66.6) + .Put(M3, 9999L) + .Put(M4, 8888L) + .Build(); + AssertContains( + metrics, + MeasurementDouble.Create(M1, 44.4), MeasurementDouble.Create(M2, 66.6), + MeasurementLong.Create(M3, 9999L), MeasurementLong.Create(M4, 8888L)); + } + + [Fact] + public void TestBuilderEmpty() + { + var metrics = MeasureMapBuilder.Builder().Build(); + AssertContains(metrics); + } + + [Fact] + public void TestBuilder() + { + var expected = new List(10); + MeasureMapBuilder builder = MeasureMapBuilder.Builder(); + for (int i = 1; i <= 10; i++) + { + expected.Add(MeasurementDouble.Create(MakeSimpleMeasureDouble("m" + i), i * 11.1)); + builder.Put(MakeSimpleMeasureDouble("m" + i), i * 11.1); + var expArray = expected.ToArray(); + AssertContains(builder.Build(), expArray); + } + } + + [Fact] + public void TestDuplicateMeasureDoubles() + { + AssertContains( + MeasureMapBuilder.Builder().Put(M1, 1.0).Put(M1, 2.0).Build(), + MeasurementDouble.Create(M1, 2.0)); + AssertContains( + MeasureMapBuilder.Builder().Put(M1, 1.0).Put(M1, 2.0).Put(M1, 3.0).Build(), + MeasurementDouble.Create(M1, 3.0)); + AssertContains( + MeasureMapBuilder.Builder().Put(M1, 1.0).Put(M2, 2.0).Put(M1, 3.0).Build(), + MeasurementDouble.Create(M1, 3.0), + MeasurementDouble.Create(M2, 2.0)); + AssertContains( + MeasureMapBuilder.Builder().Put(M1, 1.0).Put(M1, 2.0).Put(M2, 2.0).Build(), + MeasurementDouble.Create(M1, 2.0), + MeasurementDouble.Create(M2, 2.0)); + } + + [Fact] + public void TestDuplicateMeasureLongs() + { + AssertContains( + MeasureMapBuilder.Builder().Put(M3, 100L).Put(M3, 100L).Build(), + MeasurementLong.Create(M3, 100L)); + AssertContains( + MeasureMapBuilder.Builder().Put(M3, 100L).Put(M3, 200L).Put(M3, 300L).Build(), + MeasurementLong.Create(M3, 300L)); + AssertContains( + MeasureMapBuilder.Builder().Put(M3, 100L).Put(M4, 200L).Put(M3, 300L).Build(), + MeasurementLong.Create(M3, 300L), + MeasurementLong.Create(M4, 200L)); + AssertContains( + MeasureMapBuilder.Builder().Put(M3, 100L).Put(M3, 200L).Put(M4, 200L).Build(), + MeasurementLong.Create(M3, 200L), + MeasurementLong.Create(M4, 200L)); + } + + [Fact] + public void TestDuplicateMeasures() + { + AssertContains( + MeasureMapBuilder.Builder().Put(M3, 100L).Put(M1, 1.0).Put(M3, 300L).Build(), + MeasurementLong.Create(M3, 300L), + MeasurementDouble.Create(M1, 1.0)); + AssertContains( + MeasureMapBuilder.Builder().Put(M2, 2.0).Put(M3, 100L).Put(M2, 3.0).Build(), + MeasurementDouble.Create(M2, 3.0), + MeasurementLong.Create(M3, 100L)); + } + + private static IMeasureDouble MakeSimpleMeasureDouble(String measure) + { + return MeasureDouble.Create(measure, measure + " description", "1"); + } + + private static IMeasureLong MakeSimpleMeasureLong(String measure) + { + return MeasureLong.Create(measure, measure + " description", "1"); + } + + private static void AssertContains(IEnumerable metrics, params IMeasurement[] measurements) + { + var expected = measurements.ToList(); + Assert.True(Collections.AreEquivalent(metrics, expected)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/MeasureTest.cs b/test/OpenCensus.Tests/Impl/Stats/MeasureTest.cs new file mode 100644 index 000000000..1964d2d21 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/MeasureTest.cs @@ -0,0 +1,119 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Stats.Measures; + using Xunit; + + public class MeasureTest + { + [Fact] + public void TestConstants() + { + Assert.Equal(255, Measure.NameMaxLength); + } + + [Fact] + public void PreventTooLongMeasureName() + { + char[] chars = new char[Measure.NameMaxLength + 1]; + + for (int i = 0; i < chars.Length; i++) + { + chars[i] = 'a'; + } + + String longName = new string(chars); + Assert.Throws(() => MeasureDouble.Create(longName, "description", "1")); + } + + [Fact] + public void PreventNonPrintableMeasureName() + { + Assert.Throws(() => MeasureDouble.Create("\u0002", "description", "1")); + } + + [Fact] + public void TestMeasureDoubleComponents() + { + IMeasure measurement = MeasureDouble.Create("Foo", "The description of Foo", "Mbit/s"); + Assert.Equal("Foo", measurement.Name); + Assert.Equal("The description of Foo", measurement.Description); + Assert.Equal("Mbit/s", measurement.Unit); + } + + [Fact] + public void Testmeasurelongcomponents() + { + IMeasure measurement = MeasureLong.Create("Bar", "The description of Bar", "1"); + Assert.Equal("Bar", measurement.Name); + Assert.Equal("The description of Bar", measurement.Description); + Assert.Equal("1", measurement.Unit); + } + + [Fact] + public void TestMeasureDoubleEquals() + { + Assert.Equal(MeasureDouble.Create("name", "description", "bit/s"), MeasureDouble.Create("name", "description", "bit/s")); + Assert.NotEqual(MeasureDouble.Create("name", "description", "bit/s"), MeasureDouble.Create("name", "description 2", "bit/s")); + } + + [Fact] + public void TestMeasureLongEquals() + { + Assert.Equal(MeasureLong.Create("name", "description", "bit/s"), MeasureLong.Create("name", "description", "bit/s")); + Assert.NotEqual(MeasureLong.Create("name", "description", "bit/s"), MeasureLong.Create("name", "description 2", "bit/s")); + } + + [Fact] + public void TestMatch() + { + List measures = + new List() { + MeasureDouble.Create("measure1", "description", "1"), + MeasureLong.Create("measure2", "description", "1"),}; + List outputs = new List(); + foreach (IMeasure measure in measures) + { + outputs.Add( + measure.Match( + (arg) => + { + return "double"; + }, + (arg) => + { + return "long"; + }, + (arg) => + { + throw new ArgumentException(); + })); + } + + Assert.Equal(new List() { "double", "long" }, outputs); + } + + [Fact] + public void TestMeasureDoubleIsNotEqualToMeasureLong() + { + Assert.NotEqual(MeasureDouble.Create("name", "description", "bit/s"), (IMeasure)MeasureLong.Create("name", "description", "bit/s")); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/MeasureToViewMapTest.cs b/test/OpenCensus.Tests/Impl/Stats/MeasureToViewMapTest.cs new file mode 100644 index 000000000..b877da602 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/MeasureToViewMapTest.cs @@ -0,0 +1,52 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + + public class MeasureToViewMapTest + { + private static readonly IMeasure MEASURE = MeasureDouble.Create("my measurement", "measurement description", "By"); + + private static readonly IViewName VIEW_NAME = ViewName.Create("my view"); + + // private static readonly Cumulative CUMULATIVE = Cumulative.create(); + + private static readonly IView VIEW = + View.Create( + VIEW_NAME, + "view description", + MEASURE, + Mean.Create(), + new List() { TagKey.Create("my key") }); + + [Fact] + public void TestRegisterAndGetView() + { + MeasureToViewMap measureToViewMap = new MeasureToViewMap(); + measureToViewMap.RegisterView(VIEW); + IViewData viewData = measureToViewMap.GetView(VIEW_NAME, StatsCollectionState.ENABLED); + Assert.Equal(VIEW, viewData.View); + Assert.Empty(viewData.AggregationMap); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/MutableAggregationTest.cs b/test/OpenCensus.Tests/Impl/Stats/MutableAggregationTest.cs new file mode 100644 index 000000000..df3c6ea38 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/MutableAggregationTest.cs @@ -0,0 +1,256 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using Xunit; + + public class MutableAggregationTest + { + private const double TOLERANCE = 1e-6; + private static readonly IBucketBoundaries BUCKET_BOUNDARIES = BucketBoundaries.Create(new List() { -10.0, 0.0, 10.0 }); + + [Fact] + public void TestCreateEmpty() + { + Assert.InRange(MutableSum.Create().Sum, 0 - TOLERANCE, 0 + TOLERANCE); + Assert.Equal(0, MutableCount.Create().Count); + Assert.InRange(MutableMean.Create().Mean, 0 - TOLERANCE, 0 + TOLERANCE); + Assert.True(Double.IsNaN(MutableLastValue.Create().LastValue)); + + IBucketBoundaries bucketBoundaries = BucketBoundaries.Create(new List() { 0.1, 2.2, 33.3 }); + MutableDistribution mutableDistribution = MutableDistribution.Create(bucketBoundaries); + Assert.InRange(mutableDistribution.Mean, 0, TOLERANCE); + Assert.Equal(0, mutableDistribution.Count); + Assert.Equal(double.PositiveInfinity, mutableDistribution.Min); + Assert.Equal(double.NegativeInfinity, mutableDistribution.Max); + Assert.InRange(mutableDistribution.SumOfSquaredDeviations, 0 - TOLERANCE, 0 + TOLERANCE); + Assert.Equal(new long[4], mutableDistribution.BucketCounts); + } + + [Fact] + public void TestNullBucketBoundaries() + { + + Assert.Throws(() => MutableDistribution.Create(null)); + } + + [Fact] + public void TestNoBoundaries() + { + List buckets = new List(); + MutableDistribution noBoundaries = MutableDistribution.Create(BucketBoundaries.Create(buckets)); + Assert.Single(noBoundaries.BucketCounts); + Assert.Equal(0, noBoundaries.BucketCounts[0]); + } + + [Fact] + public void TestAdd() + { + List aggregations = + new List(){ + MutableSum.Create(), + MutableCount.Create(), + MutableMean.Create(), + MutableDistribution.Create(BUCKET_BOUNDARIES), + MutableLastValue.Create(),}; + + List values = new List() { -1.0, 1.0, -5.0, 20.0, 5.0 }; + + foreach (double value in values) + { + foreach (MutableAggregation aggregation in aggregations) + { + aggregation.Add(value); + } + } + + foreach (MutableAggregation aggregation in aggregations) + { + aggregation.Match( + (arg) => + { + Assert.InRange(arg.Sum, 20.0 - TOLERANCE, 20.0 + TOLERANCE); + return null; + }, + (arg) => + + { + Assert.Equal(5, arg.Count); + return null; + + }, + (arg) => + + { + Assert.InRange(arg.Mean, 4.0 - TOLERANCE, 4.0 + TOLERANCE); + Assert.InRange(arg.Max, 20.0 - TOLERANCE, 20 + TOLERANCE); + Assert.InRange(arg.Min, -5.0 - TOLERANCE, -5.0 + TOLERANCE); + return null; + + }, + (arg) => + { + Assert.Equal(new long[] { 0, 2, 2, 1 }, arg.BucketCounts); + return null; + }, + (arg) => + { + Assert.InRange(arg.LastValue, 5.0 - TOLERANCE, 5.0 + TOLERANCE); + return null; + } + ); + } + } + + [Fact] + public void TestMatch() + { + List aggregations = + new List(){ + MutableSum.Create(), + MutableCount.Create(), + MutableMean.Create(), + MutableDistribution.Create(BUCKET_BOUNDARIES), + MutableLastValue.Create(),}; + + List actual = new List(); + foreach (MutableAggregation aggregation in aggregations) + { + actual.Add( + aggregation.Match( + (arg) => + { + return "SUM"; + + }, + (arg) => + + { + return "COUNT"; + }, + (arg) => + { + return "MEAN"; + }, + (arg) => + { + return "DISTRIBUTION"; + }, + (arg) => + { + return "LASTVALUE"; + } + ) + ); + } + + Assert.Equal(new List() { "SUM", "COUNT", "MEAN", "DISTRIBUTION", "LASTVALUE" }, actual); + } + + [Fact] + public void TestCombine_SumCountMean() + { + // combine() for Mutable Sum, Count and Mean will pick up fractional stats + List aggregations1 = + new List() { MutableSum.Create(), MutableCount.Create(), MutableMean.Create() }; + List aggregations2 = + new List() { MutableSum.Create(), MutableCount.Create(), MutableMean.Create() }; + + foreach (double val in new List() { -1.0, -5.0 }) + { + foreach (MutableAggregation aggregation in aggregations1) + { + aggregation.Add(val); + } + } + foreach (double val in new List() { 10.0, 50.0 }) + { + foreach (MutableAggregation aggregation in aggregations2) + { + aggregation.Add(val); + } + } + + List combined = + new List() { MutableSum.Create(), MutableCount.Create(), MutableMean.Create() }; + double fraction1 = 1.0; + double fraction2 = 0.6; + for (int i = 0; i < combined.Count; i++) + { + combined[i].Combine(aggregations1[i], fraction1); + combined[i].Combine(aggregations2[i], fraction2); + } + + Assert.InRange(((MutableSum)combined[0]).Sum, 30 - TOLERANCE, 30 + TOLERANCE); + Assert.Equal(3, ((MutableCount)combined[1]).Count); + Assert.InRange(((MutableMean)combined[2]).Mean, 10 - TOLERANCE, 10 + TOLERANCE); + } + + [Fact] + public void TestCombine_Distribution() + { + // combine() for Mutable Distribution will ignore fractional stats + MutableDistribution distribution1 = MutableDistribution.Create(BUCKET_BOUNDARIES); + MutableDistribution distribution2 = MutableDistribution.Create(BUCKET_BOUNDARIES); + MutableDistribution distribution3 = MutableDistribution.Create(BUCKET_BOUNDARIES); + + foreach (double val in new List() { 5.0, -5.0 }) + { + distribution1.Add(val); + } + foreach (double val in new List() { 10.0, 20.0 }) + { + distribution2.Add(val); + } + foreach (double val in new List() { -10.0, 15.0, -15.0, -20.0 }) + { + distribution3.Add(val); + } + + MutableDistribution combined = MutableDistribution.Create(BUCKET_BOUNDARIES); + combined.Combine(distribution1, 1.0); // distribution1 will be combined + combined.Combine(distribution2, 0.6); // distribution2 will be ignored + VerifyMutableDistribution(combined, 0, 2, -5, 5, 50.0, new long[] { 0, 1, 1, 0 }, TOLERANCE); + + combined.Combine(distribution2, 1.0); // distribution2 will be combined + VerifyMutableDistribution(combined, 7.5, 4, -5, 20, 325.0, new long[] { 0, 1, 1, 2 }, TOLERANCE); + + combined.Combine(distribution3, 1.0); // distribution3 will be combined + VerifyMutableDistribution(combined, 0, 8, -20, 20, 1500.0, new long[] { 2, 2, 1, 3 }, TOLERANCE); + } + + private static void VerifyMutableDistribution( + MutableDistribution mutableDistribution, + double mean, + long count, + double min, + double max, + double sumOfSquaredDeviations, + long[] bucketCounts, + double tolerance) + { + Assert.InRange(mutableDistribution.Mean, mean - tolerance, mean + tolerance); + Assert.Equal(count, mutableDistribution.Count); + Assert.InRange(mutableDistribution.Min, min - tolerance, min + tolerance); + Assert.InRange(mutableDistribution.Max, max - tolerance, max + tolerance); + Assert.InRange(mutableDistribution.SumOfSquaredDeviations, sumOfSquaredDeviations - tolerance, sumOfSquaredDeviations + tolerance); + Assert.Equal(bucketCounts, mutableDistribution.BucketCounts); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/MutableViewDataTest.cs b/test/OpenCensus.Tests/Impl/Stats/MutableViewDataTest.cs new file mode 100644 index 000000000..25fbf5096 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/MutableViewDataTest.cs @@ -0,0 +1,127 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + + public class MutableViewDataTest + { + + private const double EPSILON = 1e-7; + + private static readonly ITagKey ORIGINATOR = TagKey.Create("originator"); + private static readonly ITagKey CALLER = TagKey.Create("caller"); + private static readonly ITagKey METHOD = TagKey.Create("method"); + private static readonly ITagValue CALLER_V = TagValue.Create("some caller"); + private static readonly ITagValue METHOD_V = TagValue.Create("some method"); + private static readonly IMeasureDouble MEASURE_DOUBLE = MeasureDouble.Create("measure1", "description", "1"); + private static readonly IMeasureLong MEASURE_LONG = MeasureLong.Create("measure2", "description", "1"); + + [Fact] + public void TestConstants() + { + Assert.Null(MutableViewData.UnknownTagValue); + Assert.Equal(Timestamp.Create(0, 0), MutableViewData.ZeroTimestamp); + } + + [Fact] + public void TestGetTagValues() + { + IReadOnlyList columns = new List() { CALLER, METHOD, ORIGINATOR }; + IDictionary tags = new Dictionary() { { CALLER, CALLER_V }, { METHOD, METHOD_V } }; + + Assert.Equal(new List() { CALLER_V, METHOD_V, MutableViewData.UnknownTagValue }, + MutableViewData.GetTagValues(tags, columns)); + + } + + [Fact] + public void CreateMutableAggregation() + { + IBucketBoundaries bucketBoundaries = BucketBoundaries.Create(new List() { -1.0, 0.0, 1.0 }); + + Assert.InRange(((MutableSum)MutableViewData.CreateMutableAggregation(Sum.Create())).Sum, 0.0 - EPSILON, 0.0 + EPSILON); + Assert.Equal(0, ((MutableCount)MutableViewData.CreateMutableAggregation(Count.Create())).Count); + Assert.InRange(((MutableMean)MutableViewData.CreateMutableAggregation(Mean.Create())).Mean, 0.0 - EPSILON, 0.0 + EPSILON); + Assert.True(Double.IsNaN( ((MutableLastValue)MutableViewData.CreateMutableAggregation(LastValue.Create())).LastValue)); + + MutableDistribution mutableDistribution = + (MutableDistribution)MutableViewData.CreateMutableAggregation(Distribution.Create(bucketBoundaries)); + Assert.Equal(double.PositiveInfinity, mutableDistribution.Min); + Assert.Equal(double.NegativeInfinity, mutableDistribution.Max); + Assert.InRange(mutableDistribution.SumOfSquaredDeviations, 0.0 - EPSILON, 0.0 + EPSILON); + Assert.Equal(new long[4], mutableDistribution.BucketCounts); + } + + [Fact] + public void CreateAggregationData() + { + IBucketBoundaries bucketBoundaries = BucketBoundaries.Create(new List() { -1.0, 0.0, 1.0 }); + List mutableAggregations = + new List() { + MutableCount.Create(), + MutableMean.Create(), + MutableDistribution.Create(bucketBoundaries),}; + List aggregates = new List(); + + aggregates.Add(MutableViewData.CreateAggregationData(MutableSum.Create(), MEASURE_DOUBLE)); + aggregates.Add(MutableViewData.CreateAggregationData(MutableSum.Create(), MEASURE_LONG)); + aggregates.Add(MutableViewData.CreateAggregationData(MutableLastValue.Create(), MEASURE_DOUBLE)); + aggregates.Add(MutableViewData.CreateAggregationData(MutableLastValue.Create(), MEASURE_LONG)); + + foreach (MutableAggregation mutableAggregation in mutableAggregations) + { + aggregates.Add(MutableViewData.CreateAggregationData(mutableAggregation, MEASURE_DOUBLE)); + } + List expected = new List() + { + SumDataDouble.Create(0), + SumDataLong.Create(0), + LastValueDataDouble.Create(Double.NaN), + LastValueDataLong.Create(0), + CountData.Create(0), + MeanData.Create(0, 0, Double.MaxValue, Double.MinValue), + DistributionData.Create( + 0, + 0, + Double.PositiveInfinity, + Double.NegativeInfinity, + 0, + new List() { 0L, 0L, 0L, 0L }), + }; + Assert.Equal(expected, aggregates); + + } + + [Fact] + public void TestDurationToMillis() + { + Assert.Equal(0, MutableViewData.ToMillis(Duration.Create(0, 0))); + Assert.Equal(987, MutableViewData.ToMillis(Duration.Create(0, 987000000))); + Assert.Equal(3456, MutableViewData.ToMillis(Duration.Create(3, 456000000))); + Assert.Equal(-1, MutableViewData.ToMillis(Duration.Create(0, -1000000))); + Assert.Equal(-1000, MutableViewData.ToMillis(Duration.Create(-1, 0))); + Assert.Equal(-3456, MutableViewData.ToMillis(Duration.Create(-3, -456000000))); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/NoopStatsTest.cs b/test/OpenCensus.Tests/Impl/Stats/NoopStatsTest.cs new file mode 100644 index 000000000..64af48b26 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/NoopStatsTest.cs @@ -0,0 +1,111 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections; + using System.Collections.Generic; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + + public class NoopStatsTest + { + private static readonly ITag TAG = Tag.Create(TagKey.Create("key"), TagValue.Create("value")); + private static readonly IMeasureDouble MEASURE = MeasureDouble.Create("my measure", "description", "s"); + + private readonly ITagContext tagContext = new TestTagContext(); + + + [Fact] + public void NoopStatsComponent() + { + Assert.Same(NoopStats.NoopStatsRecorder, NoopStats.NewNoopStatsComponent().StatsRecorder); + Assert.Equal(NoopStats.NewNoopViewManager().GetType(), NoopStats.NewNoopStatsComponent().ViewManager.GetType()); + } + + [Fact] + public void NoopStatsComponent_GetState() + { + Assert.Equal(StatsCollectionState.DISABLED, NoopStats.NewNoopStatsComponent().State); + } + + // [Fact] + // public void NoopStatsComponent_SetState_IgnoresInput() + // { + // IStatsComponent noopStatsComponent = NoopStats.NewNoopStatsComponent(); + // noopStatsComponent.State = StatsCollectionState.ENABLED; + // assertThat(noopStatsComponent.getState()).isEqualTo(StatsCollectionState.DISABLED); + // } + + // [Fact] + // public void NoopStatsComponent_SetState_DisallowsNull() + // { + // StatsComponent noopStatsComponent = NoopStats.newNoopStatsComponent(); + // thrown.expect(NullPointerException); + // noopStatsComponent.setState(null); + // } + + // [Fact] + // public void NoopStatsComponent_DisallowsSetStateAfterGetState() + // { + // StatsComponent noopStatsComponent = NoopStats.newNoopStatsComponent(); + // noopStatsComponent.setState(StatsCollectionState.DISABLED); + // noopStatsComponent.getState(); + // thrown.expect(IllegalStateException); + // thrown.expectMessage("State was already read, cannot set state."); + // noopStatsComponent.setState(StatsCollectionState.ENABLED); + // } + + // The NoopStatsRecorder should do nothing, so this test just checks that record doesn't throw an + // exception. + [Fact] + public void NoopStatsRecorder_Record() + { + NoopStats.NoopStatsRecorder.NewMeasureMap().Put(MEASURE, 5).Record(tagContext); + } + + // The NoopStatsRecorder should do nothing, so this test just checks that record doesn't throw an + // exception. + [Fact] + public void NoopStatsRecorder_RecordWithCurrentContext() + { + NoopStats.NoopStatsRecorder.NewMeasureMap().Put(MEASURE, 6).Record(); + } + + [Fact] + public void NoopStatsRecorder_Record_DisallowNullTagContext() + { + IMeasureMap measureMap = NoopStats.NoopStatsRecorder.NewMeasureMap(); + Assert.Throws(() => measureMap.Record(null)); + } + + class TestTagContext : ITagContext + { + public IEnumerator GetEnumerator() + { + var l = new List() { TAG }; + return l.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/NoopViewManagerTest.cs b/test/OpenCensus.Tests/Impl/Stats/NoopViewManagerTest.cs new file mode 100644 index 000000000..7cfa5d923 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/NoopViewManagerTest.cs @@ -0,0 +1,174 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + + public class NoopViewManagerTest + { + private static readonly IMeasureDouble MEASURE = MeasureDouble.Create("my measure", "description", "s"); + private static readonly ITagKey KEY = TagKey.Create("KEY"); + private static readonly IViewName VIEW_NAME = ViewName.Create("my view"); + private static readonly String VIEW_DESCRIPTION = "view description"; + private static readonly ISum AGGREGATION = Sum.Create(); + // private static readonly Cumulative CUMULATIVE = Cumulative.create(); + private static readonly Duration TEN_SECONDS = Duration.Create(10, 0); + // private static readonly Interval INTERVAL = Interval.create(TEN_SECONDS); + + // @Rule public readonly ExpectedException thrown = ExpectedException.none(); + + [Fact] + public void NoopViewManager_RegisterView_DisallowRegisteringDifferentViewWithSameName() + { + IView view1 = + View.Create( + VIEW_NAME, "description 1", MEASURE, AGGREGATION, new List { KEY }); + IView view2 = + View.Create( + VIEW_NAME, "description 2", MEASURE, AGGREGATION, new List { KEY }); + IViewManager viewManager = NoopStats.NewNoopViewManager(); + viewManager.RegisterView(view1); + + try + { + Assert.Throws(() => viewManager.RegisterView(view2)); + } + finally + { + Assert.Equal(view1, viewManager.GetView(VIEW_NAME).View); + } + } + + [Fact] + public void NoopViewManager_RegisterView_AllowRegisteringSameViewTwice() + { + IView view = + View.Create( + VIEW_NAME, VIEW_DESCRIPTION, MEASURE, AGGREGATION, new List { KEY }); + IViewManager viewManager = NoopStats.NewNoopViewManager(); + viewManager.RegisterView(view); + viewManager.RegisterView(view); + } + + [Fact] + public void NoopViewManager_RegisterView_DisallowNull() + { + IViewManager viewManager = NoopStats.NewNoopViewManager(); + Assert.Throws(() => viewManager.RegisterView(null)); + } + + [Fact] + public void NoopViewManager_GetView_GettingNonExistentViewReturnsNull() + { + IViewManager viewManager = NoopStats.NewNoopViewManager(); + Assert.Null(viewManager.GetView(VIEW_NAME)); + } + + [Fact] + public void NoopViewManager_GetView_Cumulative() + { + IView view = + View.Create( + VIEW_NAME, VIEW_DESCRIPTION, MEASURE, AGGREGATION, new List { KEY }); + IViewManager viewManager = NoopStats.NewNoopViewManager(); + viewManager.RegisterView(view); + + IViewData viewData = viewManager.GetView(VIEW_NAME); + Assert.Equal(view, viewData.View); + Assert.Empty(viewData.AggregationMap); + Assert.Equal(DateTimeOffset.MinValue, viewData.Start); + Assert.Equal(DateTimeOffset.MinValue, viewData.End); + + } + + [Fact] + public void noopViewManager_GetView_Interval() + { + IView view = + View.Create( + VIEW_NAME, VIEW_DESCRIPTION, MEASURE, AGGREGATION, new List { KEY }); + IViewManager viewManager = NoopStats.NewNoopViewManager(); + viewManager.RegisterView(view); + + IViewData viewData = viewManager.GetView(VIEW_NAME); + Assert.Equal(view, viewData.View); + Assert.Empty(viewData.AggregationMap); + Assert.Equal(DateTimeOffset.MinValue, viewData.Start); + Assert.Equal(DateTimeOffset.MinValue, viewData.End); + + } + + [Fact] + public void NoopViewManager_GetView_DisallowNull() + { + IViewManager viewManager = NoopStats.NewNoopViewManager(); + Assert.Throws(() => viewManager.GetView(null)); + } + + [Fact] + public void GetAllExportedViews() + { + IViewManager viewManager = NoopStats.NewNoopViewManager(); + Assert.Empty(viewManager.AllExportedViews); + IView cumulativeView1 = + View.Create( + ViewName.Create("View 1"), + VIEW_DESCRIPTION, + MEASURE, + AGGREGATION, + new List { KEY }); + IView cumulativeView2 = + View.Create( + ViewName.Create("View 2"), + VIEW_DESCRIPTION, + MEASURE, + AGGREGATION, + new List { KEY }); + + + viewManager.RegisterView(cumulativeView1); + viewManager.RegisterView(cumulativeView2); + + // Only cumulative views should be exported. + Assert.Equal(2, viewManager.AllExportedViews.Count); + Assert.Contains(cumulativeView1, viewManager.AllExportedViews); + Assert.Contains(cumulativeView2, viewManager.AllExportedViews); + } + + [Fact] + public void GetAllExportedViews_ResultIsUnmodifiable() + { + IViewManager viewManager = NoopStats.NewNoopViewManager(); + IView view1 = + View.Create( + ViewName.Create("View 1"), VIEW_DESCRIPTION, MEASURE, AGGREGATION, new List { KEY }); + viewManager.RegisterView(view1); + ISet exported = viewManager.AllExportedViews; + + IView view2 = + View.Create( + ViewName.Create("View 2"), VIEW_DESCRIPTION, MEASURE, AGGREGATION, new List { KEY }); + Assert.Throws(() => exported.Add(view2)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/QuickStartExampleTest.cs b/test/OpenCensus.Tests/Impl/Stats/QuickStartExampleTest.cs new file mode 100644 index 000000000..35bfb3df7 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/QuickStartExampleTest.cs @@ -0,0 +1,224 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + using Xunit.Abstractions; + + public class QuickStartExampleTest + { + ITestOutputHelper output; + + public QuickStartExampleTest(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void Main() + { + var statsComponent = new StatsComponent(); + var viewManager = statsComponent.ViewManager; + var statsRecorder = statsComponent.StatsRecorder; + var tagsComponent = new TagsComponent(); + var tagger = tagsComponent.Tagger; + + ITagKey FRONTEND_KEY = TagKey.Create("my.org/keys/frontend"); + ITagKey FRONTEND_OS_KEY = TagKey.Create("my.org/keys/frontend/os"); + ITagKey FRONTEND_OS_VERSION_KEY = TagKey.Create("my.org/keys/frontend/os/version"); + + IMeasureLong VIDEO_SIZE = MeasureLong.Create("my.org/measure/video_size", "size of processed videos", "MBy"); + + IViewName VIDEO_SIZE_BY_FRONTEND_VIEW_NAME = ViewName.Create("my.org/views/video_size_byfrontend"); + IView VIDEO_SIZE_BY_FRONTEND_VIEW = View.Create( + VIDEO_SIZE_BY_FRONTEND_VIEW_NAME, + "processed video size over time", + VIDEO_SIZE, + Distribution.Create(BucketBoundaries.Create(new List() { 0.0, 256.0, 65536.0 })), + new List() { FRONTEND_KEY}); + + IViewName VIDEO_SIZE_ALL_VIEW_NAME = ViewName.Create("my.org/views/video_size_all"); + IView VIDEO_SIZE_VIEW_ALL = View.Create( + VIDEO_SIZE_ALL_VIEW_NAME, + "processed video size over time", + VIDEO_SIZE, + Distribution.Create(BucketBoundaries.Create(new List() { 0.0, 256.0, 65536.0 })), + new List() { }); + + + IViewName VIDEO_SIZE_TOTAL_VIEW_NAME = ViewName.Create("my.org/views/video_size_total"); + IView VIDEO_SIZE_TOTAL = View.Create( + VIDEO_SIZE_TOTAL_VIEW_NAME, + "total video size over time", + VIDEO_SIZE, + Sum.Create(), + new List() { FRONTEND_KEY}); + + IViewName VIDEOS_PROCESSED_VIEW_NAME = ViewName.Create("my.org/views/videos_processed"); + IView VIDEOS_PROCESSED = View.Create( + VIDEOS_PROCESSED_VIEW_NAME, + "total video processed", + VIDEO_SIZE, + Count.Create(), + new List() { FRONTEND_KEY }); + + viewManager.RegisterView(VIDEO_SIZE_VIEW_ALL); + viewManager.RegisterView(VIDEO_SIZE_BY_FRONTEND_VIEW); + viewManager.RegisterView(VIDEO_SIZE_TOTAL); + viewManager.RegisterView(VIDEOS_PROCESSED); + + ITagContext context1 = tagger + .EmptyBuilder + .Put(FRONTEND_KEY, TagValue.Create("front1")) + .Build(); + ITagContext context2 = tagger + .EmptyBuilder + .Put(FRONTEND_KEY, TagValue.Create("front2")) + .Build(); + + long sum = 0; + for (int i = 0; i < 10; i++) + { + sum = sum + (25648 * i); + if (i % 2 == 0) + { + statsRecorder.NewMeasureMap().Put(VIDEO_SIZE, 25648 * i).Record(context1); + } else + { + statsRecorder.NewMeasureMap().Put(VIDEO_SIZE, 25648 * i).Record(context2); + } + } + + IViewData viewDataByFrontend = viewManager.GetView(VIDEO_SIZE_BY_FRONTEND_VIEW_NAME); + var viewDataAggMap = viewDataByFrontend.AggregationMap.ToList(); + output.WriteLine(viewDataByFrontend.ToString()); + + IViewData viewDataAll = viewManager.GetView(VIDEO_SIZE_ALL_VIEW_NAME); + var viewDataAggMapAll = viewDataAll.AggregationMap.ToList(); + output.WriteLine(viewDataAll.ToString()); + + IViewData viewData1 = viewManager.GetView(VIDEO_SIZE_TOTAL_VIEW_NAME); + var viewData1AggMap = viewData1.AggregationMap.ToList(); + output.WriteLine(viewData1.ToString()); + + IViewData viewData2 = viewManager.GetView(VIDEOS_PROCESSED_VIEW_NAME); + var viewData2AggMap = viewData2.AggregationMap.ToList(); + output.WriteLine(viewData2.ToString()); + + output.WriteLine(sum.ToString()); + } + + [Fact] + public void Main2() + { + var statsComponent = new StatsComponent(); + var viewManager = statsComponent.ViewManager; + var statsRecorder = statsComponent.StatsRecorder; + var tagsComponent = new TagsComponent(); + var tagger = tagsComponent.Tagger; + + ITagKey FRONTEND_KEY = TagKey.Create("my.org/keys/frontend"); + ITagKey FRONTEND_OS_KEY = TagKey.Create("my.org/keys/frontend/os"); + ITagKey FRONTEND_OS_VERSION_KEY = TagKey.Create("my.org/keys/frontend/os/version"); + + IMeasureLong VIDEO_SIZE = MeasureLong.Create("my.org/measure/video_size", "size of processed videos", "MBy"); + + IViewName VIDEO_SIZE_VIEW_NAME = ViewName.Create("my.org/views/video_size_byfrontend"); + IView VIDEO_SIZE_VIEW = View.Create( + VIDEO_SIZE_VIEW_NAME, + "processed video size over time", + VIDEO_SIZE, + Distribution.Create(BucketBoundaries.Create(new List() { 0.0, 256.0, 65536.0 })), + new List() { FRONTEND_KEY, FRONTEND_OS_KEY, FRONTEND_OS_VERSION_KEY }); + + + viewManager.RegisterView(VIDEO_SIZE_VIEW); + + + ITagContext context1 = tagger + .EmptyBuilder + .Put(FRONTEND_KEY, TagValue.Create("front1")) + .Put(FRONTEND_OS_KEY, TagValue.Create("windows")) + .Build(); + ITagContext context2 = tagger + .EmptyBuilder + .Put(FRONTEND_KEY, TagValue.Create("front2")) + .Put(FRONTEND_OS_VERSION_KEY, TagValue.Create("1.1.1")) + .Build(); + + long sum = 0; + for (int i = 0; i < 10; i++) + { + sum = sum + (25648 * i); + if (i % 2 == 0) + { + statsRecorder.NewMeasureMap().Put(VIDEO_SIZE, 25648 * i).Record(context1); + } + else + { + statsRecorder.NewMeasureMap().Put(VIDEO_SIZE, 25648 * i).Record(context2); + } + } + + IViewData videoSizeView = viewManager.GetView(VIDEO_SIZE_VIEW_NAME); + var viewDataAggMap = videoSizeView.AggregationMap.ToList(); + var view = viewManager.AllExportedViews.ToList()[0]; + for (int i = 0; i < view.Columns.Count; i++) + { + output.WriteLine(view.Columns[i] + "=" + GetTagValues(i, viewDataAggMap)); + } + + var keys = new List() { TagValue.Create("1.1.1") }; + + var results = videoSizeView.AggregationMap.Where((kvp) => + { + foreach (var key in keys) + { + if (!kvp.Key.Values.Contains(key)) + { + return false; + } + } + return true; + }); + + output.WriteLine(videoSizeView.ToString()); + + output.WriteLine(sum.ToString()); + } + + private string GetTagValues(int i, List> viewDataAggMap) + { + string result = string.Empty; + foreach (var kvp in viewDataAggMap) + { + var val = kvp.Key.Values[i]; + if (val != null) + { + result += val.AsString; + } + } + return result; + } + + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/StatsComponentTest.cs b/test/OpenCensus.Tests/Impl/Stats/StatsComponentTest.cs new file mode 100644 index 000000000..ac37c601a --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/StatsComponentTest.cs @@ -0,0 +1,65 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using OpenCensus.Internal; + using Xunit; + + public class StatsComponentTest + { + private readonly StatsComponent statsComponent = new StatsComponent(new SimpleEventQueue()); + + [Fact] + public void DefaultState() + { + Assert.Equal(StatsCollectionState.ENABLED, statsComponent.State); + } + + // [Fact] + // public void setState_Disabled() + // { + // statsComponent.setState(StatsCollectionState.DISABLED); + // assertThat(statsComponent.getState()).isEqualTo(StatsCollectionState.DISABLED); + // } + + // [Fact] + // public void setState_Enabled() + // { + // statsComponent.setState(StatsCollectionState.DISABLED); + // statsComponent.setState(StatsCollectionState.ENABLED); + // assertThat(statsComponent.getState()).isEqualTo(StatsCollectionState.ENABLED); + // } + + // [Fact] + // public void setState_DisallowsNull() + // { + // thrown.expect(NullPointerException); + // thrown.expectMessage("newState"); + // statsComponent.setState(null); + // } + + // [Fact] + // public void preventSettingStateAfterGettingState() + // { + // statsComponent.setState(StatsCollectionState.DISABLED); + // statsComponent.getState(); + // thrown.expect(IllegalStateException); + // thrown.expectMessage("State was already read, cannot set state."); + // statsComponent.setState(StatsCollectionState.ENABLED); + // } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/StatsDefaultTest.cs b/test/OpenCensus.Tests/Impl/Stats/StatsDefaultTest.cs new file mode 100644 index 000000000..f1bfe0897 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/StatsDefaultTest.cs @@ -0,0 +1,69 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using Xunit; + + public class StatsDefaultTest + { + // [Fact] + // public void loadStatsManager_UsesProvidedClassLoader() + // { + // final RuntimeException toThrow = new RuntimeException("UseClassLoader"); + // thrown.expect(RuntimeException.class); + // thrown.expectMessage("UseClassLoader"); + // Stats.loadStatsComponent( + // new ClassLoader() + // { + // @Override + // public Class loadClass(String name) + // { + // throw toThrow; + // } + // }); + // } + + // [Fact] + // public void loadStatsManager_IgnoresMissingClasses() + // { + // ClassLoader classLoader = + // new ClassLoader() { + // @Override + // public Class loadClass(String name) throws ClassNotFoundException { + // throw new ClassNotFoundException(); + // } + // }; + + // assertThat(Stats.loadStatsComponent(classLoader).getClass().getName()) + // .isEqualTo("io.opencensus.stats.NoopStats$NoopStatsComponent"); + // } + + [Fact(Skip = "Fix later when default will be changed back")] + public void DefaultValues() + { + Assert.Equal(NoopStats.NoopStatsRecorder, Stats.StatsRecorder); + Assert.Equal(NoopStats.NewNoopViewManager().GetType(), Stats.ViewManager.GetType()); + } + + [Fact(Skip = "Fix later when default will be changed back")] + public void GetState() + { + Assert.Equal(StatsCollectionState.DISABLED, Stats.State); + } + + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/StatsRecorderTest.cs b/test/OpenCensus.Tests/Impl/Stats/StatsRecorderTest.cs new file mode 100644 index 000000000..75d0d3433 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/StatsRecorderTest.cs @@ -0,0 +1,238 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Internal; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using OpenCensus.Tags.Unsafe; + using Xunit; + + public class StatsRecorderTest + { + private static readonly ITagKey KEY = TagKey.Create("KEY"); + private static readonly ITagValue VALUE = TagValue.Create("VALUE"); + private static readonly ITagValue VALUE_2 = TagValue.Create("VALUE_2"); + private static readonly IMeasureDouble MEASURE_DOUBLE = MeasureDouble.Create("my measurement", "description", "us"); + private static readonly IMeasureDouble MEASURE_DOUBLE_NO_VIEW_1 = MeasureDouble.Create("my measurement no view 1", "description", "us"); + private static readonly IMeasureDouble MEASURE_DOUBLE_NO_VIEW_2 = MeasureDouble.Create("my measurement no view 2", "description", "us"); + private static readonly IViewName VIEW_NAME = ViewName.Create("my view"); + + private StatsComponent statsComponent; + private IViewManager viewManager; + private IStatsRecorder statsRecorder; + + public StatsRecorderTest() + { + statsComponent = new StatsComponent(new SimpleEventQueue()); + viewManager = statsComponent.ViewManager; + statsRecorder = statsComponent.StatsRecorder; + } + + [Fact] + public void Record_CurrentContextNotSet() + { + IView view = + View.Create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.Create(), + new List() { KEY }); + viewManager.RegisterView(view); + statsRecorder.NewMeasureMap().Put(MEASURE_DOUBLE, 1.0).Record(); + IViewData viewData = viewManager.GetView(VIEW_NAME); + + // record() should have used the default TagContext, so the tag value should be null. + ICollection expected = new List() { TagValues.Create(new List() { null }) }; + Assert.Equal(expected, viewData.AggregationMap.Keys); + } + + [Fact] + public void Record_CurrentContextSet() + { + IView view = + View.Create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.Create(), + new List() { KEY }); + viewManager.RegisterView(view); + var orig = AsyncLocalContext.CurrentTagContext; + AsyncLocalContext.CurrentTagContext = new SimpleTagContext(Tag.Create(KEY, VALUE)); + + try + { + statsRecorder.NewMeasureMap().Put(MEASURE_DOUBLE, 1.0).Record(); + } + finally + { + AsyncLocalContext.CurrentTagContext = orig; + } + IViewData viewData = viewManager.GetView(VIEW_NAME); + + // record() should have used the given TagContext. + ICollection expected = new List() { TagValues.Create(new List() { VALUE }) }; + Assert.Equal(expected, viewData.AggregationMap.Keys); + } + + [Fact] + public void Record_UnregisteredMeasure() + { + IView view = + View.Create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.Create(), + new List() { KEY }); + viewManager.RegisterView(view); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE_NO_VIEW_1, 1.0) + .Put(MEASURE_DOUBLE, 2.0) + .Put(MEASURE_DOUBLE_NO_VIEW_2, 3.0) + .Record(new SimpleTagContext(Tag.Create(KEY, VALUE))); + + IViewData viewData = viewManager.GetView(VIEW_NAME); + + // There should be one entry. + var tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() {{ tv, StatsTestUtil.CreateAggregationData(Sum.Create(), MEASURE_DOUBLE, 2.0) }}, + 1e-6); + } + + [Fact] + public void RecordTwice() + { + IView view = + View.Create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.Create(), + new List() { KEY }); + + viewManager.RegisterView(view); + IMeasureMap statsRecord = statsRecorder.NewMeasureMap().Put(MEASURE_DOUBLE, 1.0); + statsRecord.Record(new SimpleTagContext(Tag.Create(KEY, VALUE))); + statsRecord.Record(new SimpleTagContext(Tag.Create(KEY, VALUE_2))); + IViewData viewData = viewManager.GetView(VIEW_NAME); + + // There should be two entries. + var tv = TagValues.Create(new List() { VALUE }); + var tv2 = TagValues.Create(new List() { VALUE_2 }); + + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + { tv, StatsTestUtil.CreateAggregationData(Sum.Create(), MEASURE_DOUBLE, 1.0) }, + { tv2, StatsTestUtil.CreateAggregationData(Sum.Create(), MEASURE_DOUBLE, 1.0) }, + }, + 1e-6); + } + + [Fact] + public void Record_StatsDisabled() + { + IView view = + View.Create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.Create(), + new List() { KEY }); + + viewManager.RegisterView(view); + statsComponent.State = StatsCollectionState.DISABLED; + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.0) + .Record(new SimpleTagContext(Tag.Create(KEY, VALUE))); + Assert.Equal(CreateEmptyViewData(view), viewManager.GetView(VIEW_NAME)); + } + + [Fact] + public void Record_StatsReenabled() + { + IView view = + View.Create( + VIEW_NAME, + "description", + MEASURE_DOUBLE, + Sum.Create(), + new List() { KEY }); + + viewManager.RegisterView(view); + + statsComponent.State = StatsCollectionState.DISABLED; + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.0) + .Record(new SimpleTagContext(Tag.Create(KEY, VALUE))); + Assert.Equal(CreateEmptyViewData(view), viewManager.GetView(VIEW_NAME)); + + statsComponent.State = StatsCollectionState.ENABLED; + Assert.Empty(viewManager.GetView(VIEW_NAME).AggregationMap); + // assertThat(viewManager.getView(VIEW_NAME).getWindowData()) + // .isNotEqualTo(CumulativeData.Create(ZERO_TIMESTAMP, ZERO_TIMESTAMP)); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 4.0) + .Record(new SimpleTagContext(Tag.Create(KEY, VALUE))); + TagValues tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewManager.GetView(VIEW_NAME).AggregationMap, + new Dictionary() + { + { tv, StatsTestUtil.CreateAggregationData(Sum.Create(), MEASURE_DOUBLE, 4.0) }, + }, + 1e-6); + } + + // Create an empty ViewData with the given View. + static IViewData CreateEmptyViewData(IView view) + { + return ViewData.Create( + view, + new Dictionary(), + DateTimeOffset.MinValue, DateTimeOffset.MinValue); + } + + class SimpleTagContext : TagContextBase + { + private readonly IEnumerable tags; + + public SimpleTagContext(params ITag[] tags) + { + this.tags = new List(tags); + } + + public override IEnumerator GetEnumerator() + { + return tags.GetEnumerator(); + } + } + } +} \ No newline at end of file diff --git a/test/OpenCensus.Tests/Impl/Stats/StatsTest.cs b/test/OpenCensus.Tests/Impl/Stats/StatsTest.cs new file mode 100644 index 000000000..e102ba002 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/StatsTest.cs @@ -0,0 +1,35 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using Xunit; + + public class StatsTest + { + [Fact(Skip = "Fix statics usage")] + public void GetStatsRecorder() + { + Assert.Equal(typeof(StatsRecorder), Stats.StatsRecorder.GetType()); + } + + [Fact(Skip = "Fix statics usage")] + public void GetViewManager() + { + Assert.Equal(typeof(ViewManager), Stats.ViewManager.GetType()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/StatsTestUtil.cs b/test/OpenCensus.Tests/Impl/Stats/StatsTestUtil.cs new file mode 100644 index 000000000..237bf026c --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/StatsTestUtil.cs @@ -0,0 +1,175 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Common; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Tags; + using Xunit; + + internal static class StatsTestUtil + { + static readonly Timestamp ZERO_TIMESTAMP = Timestamp.Create(0, 0); + + internal static IAggregationData CreateAggregationData(IAggregation aggregation, IMeasure measure, params double[] values) + { + MutableAggregation mutableAggregation = MutableViewData.CreateMutableAggregation(aggregation); + foreach (double value in values) + { + mutableAggregation.Add(value); + } + return MutableViewData.CreateAggregationData(mutableAggregation, measure); + } + + internal static IViewData CreateEmptyViewData(IView view) + { + return ViewData.Create( + view, + new Dictionary(), + DateTimeOffset.MinValue, DateTimeOffset.MinValue); + } + + internal static void AssertAggregationMapEquals( + IDictionary actual, + IDictionary expected, + double tolerance) + { + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.Keys, actual.Keys); + + foreach (var tagValues in actual.Keys) + { + var act = actual[tagValues]; + var exp = expected[tagValues]; + AssertAggregationDataEquals(exp, act, tolerance); + } + } + + internal static void AssertAggregationDataEquals( + IAggregationData expected, + IAggregationData actual, + double tolerance) + { + expected.Match( + (arg) => + { + Assert.IsType(actual); + Assert.InRange(((SumDataDouble)actual).Sum, arg.Sum - tolerance, arg.Sum + tolerance); + return null; + }, + (arg) => + + { + Assert.IsType(actual); + Assert.InRange(((SumDataLong)actual).Sum, arg.Sum - tolerance, arg.Sum + tolerance); + return null; + }, + (arg) => + + { + Assert.IsType(actual); + Assert.Equal(arg.Count, ((CountData)actual).Count); + return null; + }, + (arg) => + { + Assert.IsType(actual); + Assert.InRange(((MeanData)actual).Mean, arg.Mean - tolerance, arg.Mean + tolerance); + return null; + }, + (arg) => + { + Assert.IsType(actual); + AssertDistributionDataEquals(arg, (IDistributionData)actual, tolerance); + return null; + }, + (arg) => + { + Assert.IsType(actual); + Assert.InRange(((LastValueDataDouble)actual).LastValue, arg.LastValue - tolerance, arg.LastValue + tolerance); + return null; + }, + (arg) => + { + Assert.IsType(actual); + Assert.Equal(arg.LastValue, ((LastValueDataLong)actual).LastValue); + return null; + }, + (arg) => + { + throw new ArgumentException(); + }); + } + + private static void AssertDistributionDataEquals( + IDistributionData expected, + IDistributionData actual, + double tolerance) + { + + Assert.InRange(actual.Mean, expected.Mean - tolerance, expected.Mean + tolerance); + Assert.Equal(expected.Count, actual.Count); + Assert.InRange(actual.SumOfSquaredDeviations, expected.SumOfSquaredDeviations - tolerance, expected.SumOfSquaredDeviations + tolerance); + + if (expected.Max == Double.NegativeInfinity + && expected.Min == Double.PositiveInfinity) + { + Assert.True(Double.IsNegativeInfinity(actual.Max)); + Assert.True(Double.IsPositiveInfinity(actual.Min)); + } + else + { + Assert.InRange(actual.Max, expected.Max - tolerance, expected.Max + tolerance); + Assert.InRange(actual.Min, expected.Min - tolerance, expected.Min + tolerance); + } + + Assert.Equal(RemoveTrailingZeros(expected.BucketCounts), RemoveTrailingZeros((actual).BucketCounts)); + } + + private static IEnumerable RemoveTrailingZeros(IEnumerable longs) + { + if (longs == null || longs.Any()) + { + return longs; + } + + var buffer = new List(); + var result = new List(); + foreach (var item in longs) + { + if (item == 0) + { + buffer.Add(item); + } + else + { + foreach (var bufferedItem in buffer) + { + result.Add(bufferedItem); + } + buffer.Clear(); + result.Add(item); + } + } + + return result; + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/ViewDataTest.cs b/test/OpenCensus.Tests/Impl/Stats/ViewDataTest.cs new file mode 100644 index 000000000..abfee0ded --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/ViewDataTest.cs @@ -0,0 +1,295 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + + public class ViewDataTest + { + // tag keys + private static readonly ITagKey K1 = TagKey.Create("k1"); + private static readonly ITagKey K2 = TagKey.Create("k2"); + private static readonly IReadOnlyList TAG_KEYS = new List() { K1, K2 }; + + // tag values + private static readonly ITagValue V1 = TagValue.Create("v1"); + private static readonly ITagValue V2 = TagValue.Create("v2"); + private static readonly ITagValue V10 = TagValue.Create("v10"); + private static readonly ITagValue V20 = TagValue.Create("v20"); + + // private static readonly AggregationWindow CUMULATIVE = Cumulative.Create(); + // private static readonly AggregationWindow INTERVAL_HOUR = Interval.Create(Duration.Create(3600, 0)); + + private static readonly IBucketBoundaries BUCKET_BOUNDARIES = + BucketBoundaries.Create(new List() { 10.0, 20.0, 30.0, 40.0 }); + + private static readonly IAggregation DISTRIBUTION = Distribution.Create(BUCKET_BOUNDARIES); + + private static readonly IDictionary ENTRIES = + new Dictionary() { + { TagValues.Create(new List(){ V1, V2 }), DistributionData.Create(1, 1, 1, 1, 0, new List() {0L, 1L, 0L }) }, + { TagValues.Create(new List(){ V10, V20 }), DistributionData.Create(-5, 6, -20, 5, 100.1, new List() {5L, 0L, 1L }) }, + }; + + // name + private static readonly IViewName NAME = ViewName.Create("test-view"); + // description + private static readonly String DESCRIPTION = "test-view-descriptor description"; + // measure + private static readonly IMeasure MEASURE_DOUBLE = + MeasureDouble.Create("measure1", "measure description", "1"); + + private static readonly IMeasure MEASURE_LONG = + MeasureLong.Create("measure2", "measure description", "1"); + + [Fact] + public void TestCumulativeViewData() + { + IView view = View.Create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS); + var start = DateTimeOffset.FromUnixTimeMilliseconds(1000); + var end = DateTimeOffset.FromUnixTimeMilliseconds(2000); + IViewData viewData = ViewData.Create(view, ENTRIES, start, end); + Assert.Equal(view, viewData.View); + Assert.Equal(ENTRIES, viewData.AggregationMap); + } + + // [Fact] + // public void TestIntervalViewData() + // { + // View view = + // View.Create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, INTERVAL_HOUR); + // Timestamp end = Timestamp.fromMillis(2000); + // AggregationWindowData windowData = IntervalData.Create(end); + // ViewData viewData = ViewData.Create(view, ENTRIES, windowData); + // assertThat(viewData.getView()).isEqualTo(view); + // assertThat(viewData.getAggregationMap()).isEqualTo(ENTRIES); + // assertThat(viewData.getWindowData()).isEqualTo(windowData); + // } + + [Fact] + public void TestViewDataEquals() + { + IView cumulativeView = + View.Create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS); + // View intervalView = + // View.Create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, INTERVAL_HOUR); + + // new EqualsTester() + // .addEqualityGroup( + IViewData data1 = ViewData.Create( + cumulativeView, + ENTRIES, + DateTimeOffset.FromUnixTimeMilliseconds(1000), DateTimeOffset.FromUnixTimeMilliseconds(2000)); + IViewData data2 = ViewData.Create( + cumulativeView, + ENTRIES, + DateTimeOffset.FromUnixTimeMilliseconds(1000), DateTimeOffset.FromUnixTimeMilliseconds(2000)); + Assert.Equal(data1, data2); + + // .addEqualityGroup( + IViewData data3 = ViewData.Create( + cumulativeView, + ENTRIES, + DateTimeOffset.FromUnixTimeMilliseconds(1000), DateTimeOffset.FromUnixTimeMilliseconds(3000)); + Assert.NotEqual(data1, data3); + Assert.NotEqual(data2, data3); + + // .addEqualityGroup( + // IViewData data4 = ViewData.Create(intervalView, ENTRIES, IntervalData.Create(Timestamp.fromMillis(2000))), + // IViewData data5 = ViewData.Create(intervalView, ENTRIES, IntervalData.Create(Timestamp.fromMillis(2000)))) + // .addEqualityGroup( + // ViewData.Create( + // intervalView, + // Collections.< List, AggregationData > emptyMap(), + // IntervalData.Create(Timestamp.fromMillis(2000)))) + // .testEquals(); + } + + // [Fact] + // public void testAggregationWindowDataMatch() + // { + // final Timestamp start = Timestamp.fromMillis(1000); + // final Timestamp end = Timestamp.fromMillis(2000); + // final AggregationWindowData windowData1 = CumulativeData.Create(start, end); + // final AggregationWindowData windowData2 = IntervalData.Create(end); + // windowData1.match( + // new Function() { + // @Override + // public Void apply(CumulativeData windowData) + // { + // assertThat(windowData.getStart()).isEqualTo(start); + // assertThat(windowData.getEnd()).isEqualTo(end); + // return null; + // } + // }, + // new Function() { + // @Override + // public Void apply(IntervalData windowData) + // { + // fail("CumulativeData expected."); + // return null; + // } + // }, + // Functions.throwIllegalArgumentException()); + // windowData2.match( + // new Function() { + // @Override + // public Void apply(CumulativeData windowData) + // { + // fail("IntervalData expected."); + // return null; + // } + // }, + // new Function() { + // @Override + // public Void apply(IntervalData windowData) + // { + // assertThat(windowData.getEnd()).isEqualTo(end); + // return null; + // } + // }, + // Functions.throwIllegalArgumentException()); + // } + + // [Fact] + // public void preventWindowAndAggregationWindowDataMismatch() + // { + // thrown.expect(IllegalArgumentException); + // thrown.expectMessage("AggregationWindow and AggregationWindowData types mismatch. "); + // ViewData.Create( + // View.Create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, INTERVAL_HOUR), + // ENTRIES, + // CumulativeData.Create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000))); + // } + + // [Fact] + // public void preventWindowAndAggregationWindowDataMismatch2() + // { + // thrown.expect(IllegalArgumentException.class); + // thrown.expectMessage("AggregationWindow and AggregationWindowData types mismatch. "); + // ViewData.Create( + // View.Create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, CUMULATIVE), + // ENTRIES, + // IntervalData.Create(Timestamp.fromMillis(1000))); + // } + + // [Fact] + // public void PreventStartTimeLaterThanEndTime() + // { + // // thrown.expect(IllegalArgumentException.class); + // CumulativeData.Create(Timestamp.fromMillis(3000), Timestamp.fromMillis(2000)); + // } + + [Fact] + public void PreventAggregationAndAggregationDataMismatch_SumDouble_SumLong() + { + var tagValues = TagValues.Create(new List() { V1, V2 }); + AggregationAndAggregationDataMismatch( + CreateView(Sum.Create(), MEASURE_DOUBLE), + new Dictionary() + { + {tagValues, SumDataLong.Create(100) }, + }); + } + + [Fact] + public void PreventAggregationAndAggregationDataMismatch_SumLong_SumDouble() + { + var tagValues = TagValues.Create(new List() { V1, V2 }); + AggregationAndAggregationDataMismatch( + CreateView(Sum.Create(), MEASURE_LONG), + new Dictionary() + { + {tagValues, SumDataDouble.Create(100) }, + }); + } + + [Fact] + public void PreventAggregationAndAggregationDataMismatch_Count_Distribution() + { + AggregationAndAggregationDataMismatch(CreateView(Count.Create()), ENTRIES); + } + + [Fact] + public void PreventAggregationAndAggregationDataMismatch_Mean_Distribution() + { + AggregationAndAggregationDataMismatch(CreateView(Mean.Create()), ENTRIES); + } + + [Fact] + public void PreventAggregationAndAggregationDataMismatch_Distribution_Count() + { + var tagValues1 = TagValues.Create(new List() { V1, V2 }); + var tagValues2 = TagValues.Create(new List() { V10, V20 }); + AggregationAndAggregationDataMismatch( + CreateView(DISTRIBUTION), + new Dictionary() + { + { tagValues1, DistributionData.Create(1, 1, 1, 1, 0, new List() {0L, 1L, 0L }) }, + { tagValues2, CountData.Create(100) }, + }); + } + + [Fact] + public void PreventAggregationAndAggregationDataMismatch_LastValueDouble_LastValueLong() + { + var tagValues = TagValues.Create(new List() { V1, V2 }); + AggregationAndAggregationDataMismatch( + CreateView(LastValue.Create(), MEASURE_DOUBLE), + new Dictionary() + { + {tagValues, LastValueDataLong.Create(100) }, + }); + } + + [Fact] + public void PreventAggregationAndAggregationDataMismatch_LastValueLong_LastValueDouble() + { + var tagValues = TagValues.Create(new List() { V1, V2 }); + AggregationAndAggregationDataMismatch( + CreateView(LastValue.Create(), MEASURE_LONG), + new Dictionary() + { + {tagValues, LastValueDataDouble.Create(100) }, + }); + } + + private static IView CreateView(IAggregation aggregation) + { + return CreateView(aggregation, MEASURE_DOUBLE); + } + + private static IView CreateView(IAggregation aggregation, IMeasure measure) + { + return View.Create(NAME, DESCRIPTION, measure, aggregation, TAG_KEYS); + } + + private void AggregationAndAggregationDataMismatch(IView view, IDictionary entries) + { + // CumulativeData cumulativeData = + // CumulativeData.Create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000)); + // thrown.expect(IllegalArgumentException); + // thrown.expectMessage("Aggregation and AggregationData types mismatch. "); + Assert.Throws(() => ViewData.Create(view, entries, DateTimeOffset.FromUnixTimeMilliseconds(1000), DateTimeOffset.FromUnixTimeMilliseconds(2000))); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Stats/ViewManagerTest.cs b/test/OpenCensus.Tests/Impl/Stats/ViewManagerTest.cs new file mode 100644 index 000000000..249e461e1 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/ViewManagerTest.cs @@ -0,0 +1,781 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + + public class ViewManagerTest + { + private static readonly ITagKey KEY = TagKey.Create("KEY"); + private static readonly ITagValue VALUE = TagValue.Create("VALUE"); + private static readonly ITagValue VALUE_2 = TagValue.Create("VALUE_2"); + private static readonly String MEASURE_NAME = "my measurement"; + private static readonly String MEASURE_NAME_2 = "my measurement 2"; + private static readonly String MEASURE_UNIT = "us"; + private static readonly String MEASURE_DESCRIPTION = "measure description"; + private static readonly IMeasureDouble MEASURE_DOUBLE = MeasureDouble.Create(MEASURE_NAME, MEASURE_DESCRIPTION, MEASURE_UNIT); + private static readonly IMeasureLong MEASURE_LONG = MeasureLong.Create(MEASURE_NAME_2, MEASURE_DESCRIPTION, MEASURE_UNIT); + + private static readonly IViewName VIEW_NAME = ViewName.Create("my view"); + private static readonly IViewName VIEW_NAME_2 = ViewName.Create("my view 2"); + + private static readonly String VIEW_DESCRIPTION = "view description"; + + // private static readonly Cumulative CUMULATIVE = Cumulative.Create(); + + private static readonly double EPSILON = 1e-7; + private static readonly Duration TEN_SECONDS = Duration.Create(10, 0); + // private static readonly Interval INTERVAL = Interval.Create(TEN_SECONDS); + + private static readonly IBucketBoundaries BUCKET_BOUNDARIES = + BucketBoundaries.Create( + new List() { + 0.0, 0.2, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 7.0, 10.0, 15.0, 20.0, 30.0, 40.0, 50.0,}); + + private static readonly ISum SUM = Sum.Create(); + private static readonly IMean MEAN = Mean.Create(); + private static readonly IDistribution DISTRIBUTION = Distribution.Create(BUCKET_BOUNDARIES); + private static readonly ILastValue LAST_VALUE = LastValue.Create(); + + private readonly StatsComponent statsComponent; + private readonly TagsComponent tagsComponent; + + private readonly ITagger tagger; + private readonly IViewManager viewManager; + private readonly IStatsRecorder statsRecorder; + + public ViewManagerTest() + { + statsComponent = new StatsComponent(new SimpleEventQueue()); + tagsComponent = new TagsComponent(); + + tagger = tagsComponent.Tagger; + viewManager = statsComponent.ViewManager; + statsRecorder = statsComponent.StatsRecorder; + } + + private static IView CreateCumulativeView() + { + return CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY }); + } + + private static IView CreateCumulativeView( + IViewName name, IMeasure measure, IAggregation aggregation, List keys) + { + return View.Create(name, VIEW_DESCRIPTION, measure, aggregation, keys); + } + + [Fact] + public void TestRegisterAndGetCumulativeView() + { + IView view = CreateCumulativeView(); + viewManager.RegisterView(view); + Assert.Equal(view, viewManager.GetView(VIEW_NAME).View); + Assert.Empty(viewManager.GetView(VIEW_NAME).AggregationMap); + // Assert.Equal(viewManager.GetView(VIEW_NAME).getWindowData()).isInstanceOf(CumulativeData); + } + + [Fact] + public void TestGetAllExportedViews() + { + Assert.Empty(viewManager.AllExportedViews); + IView cumulativeView1 = + CreateCumulativeView( + ViewName.Create("View 1"), MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY }); + IView cumulativeView2 = + CreateCumulativeView( + ViewName.Create("View 2"), MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY }); + // View intervalView = + // View.Create( + // View.Name.Create("View 3"), + // VIEW_DESCRIPTION, + // MEASURE_DOUBLE, + // DISTRIBUTION, + // Arrays.asList(KEY), + // INTERVAL); + viewManager.RegisterView(cumulativeView1); + viewManager.RegisterView(cumulativeView2); + + // Only cumulative views should be exported. + Assert.Contains(cumulativeView1, viewManager.AllExportedViews); + Assert.Contains(cumulativeView2, viewManager.AllExportedViews); + Assert.Equal(2, viewManager.AllExportedViews.Count); + } + + [Fact] + public void GetAllExportedViewsResultIsUnmodifiable() + { + IView view1 = + View.Create( + ViewName.Create("View 1"), + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + DISTRIBUTION, + new List() { KEY }); + viewManager.RegisterView(view1); + ISet exported = viewManager.AllExportedViews; + + IView view2 = + View.Create( + ViewName.Create("View 2"), + VIEW_DESCRIPTION, + MEASURE_DOUBLE, + DISTRIBUTION, + new List() { KEY }); + Assert.Throws(() => exported.Add(view2)); + + } + + // [Fact] + // public void TestRegisterAndGetIntervalView() + // { + // View intervalView = + // View.Create( + // VIEW_NAME, + // VIEW_DESCRIPTION, + // MEASURE_DOUBLE, + // DISTRIBUTION, + // Arrays.asList(KEY), + // INTERVAL); + // viewManager.RegisterView(intervalView); + // Assert.Equal(viewManager.GetView(VIEW_NAME).GetView()).isEqualTo(intervalView); + // Assert.Equal(viewManager.GetView(VIEW_NAME).AggregationMap).isEmpty(); + // Assert.Equal(viewManager.GetView(VIEW_NAME).getWindowData()).isInstanceOf(IntervalData.class); + // } + + [Fact] + public void AllowRegisteringSameViewTwice() + { + IView view = CreateCumulativeView(); + viewManager.RegisterView(view); + viewManager.RegisterView(view); + Assert.Equal(view, viewManager.GetView(VIEW_NAME).View); + } + + [Fact] + public void PreventRegisteringDifferentViewWithSameName() + { + IView view1 = + View.Create( + VIEW_NAME, + "View description.", + MEASURE_DOUBLE, + DISTRIBUTION, + new List() { KEY }); + IView view2 = + View.Create( + VIEW_NAME, + "This is a different description.", + MEASURE_DOUBLE, + DISTRIBUTION, + new List() { KEY }); + TestFailedToRegisterView(view1, view2, "A different view with the same name is already registered"); + } + + [Fact] + public void PreventRegisteringDifferentMeasureWithSameName() + { + IMeasureDouble measure1 = MeasureDouble.Create("measure", "description", "1"); + IMeasureLong measure2 = MeasureLong.Create("measure", "description", "1"); + IView view1 = + View.Create( + VIEW_NAME, VIEW_DESCRIPTION, measure1, DISTRIBUTION, new List() { KEY }); + IView view2 = + View.Create( + VIEW_NAME_2, VIEW_DESCRIPTION, measure2, DISTRIBUTION, new List() { KEY }); + TestFailedToRegisterView(view1, view2, "A different measure with the same name is already registered"); + } + + private void TestFailedToRegisterView(IView view1, IView view2, String message) + { + viewManager.RegisterView(view1); + try + { + Assert.Throws(() => viewManager.RegisterView(view2)); + } finally { + Assert.Equal(view1, viewManager.GetView(VIEW_NAME).View); + } + } + + [Fact] + public void ReturnNullWhenGettingNonexistentViewData() + { + Assert.Null(viewManager.GetView(VIEW_NAME)); + } + + [Fact] + public void TestRecordDouble_Distribution_Cumulative() + { + TestRecordCumulative(MEASURE_DOUBLE, DISTRIBUTION, 10.0, 20.0, 30.0, 40.0); + } + + [Fact] + public void TestRecordLong_Distribution_Cumulative() + { + TestRecordCumulative(MEASURE_LONG, DISTRIBUTION, 1000, 2000, 3000, 4000); + } + + [Fact] + public void TestRecordDouble_Sum_Cumulative() + { + TestRecordCumulative(MEASURE_DOUBLE, SUM, 11.1, 22.2, 33.3, 44.4); + } + + [Fact] + public void TestRecordLong_Sum_Cumulative() + { + TestRecordCumulative(MEASURE_LONG, SUM, 1000, 2000, 3000, 4000); + } + + [Fact] + public void TestRecordDouble_Lastvalue_Cumulative() + { + TestRecordCumulative(MEASURE_DOUBLE, LAST_VALUE, 11.1, 22.2, 33.3, 44.4); + } + + [Fact] + public void TestRecordLong_Lastvalue_Cumulative() + { + TestRecordCumulative(MEASURE_LONG, LAST_VALUE, 1000, 2000, 3000, 4000); + } + + private void TestRecordCumulative(IMeasure measure, IAggregation aggregation, params double[] values) + { + IView view = CreateCumulativeView(VIEW_NAME, measure, aggregation, new List() { KEY }); + viewManager.RegisterView(view); + ITagContext tags = tagger.EmptyBuilder.Put(KEY, VALUE).Build(); + foreach (double val in values) + { + PutToMeasureMap(statsRecorder.NewMeasureMap(), measure, val).Record(tags); + } + IViewData viewData = viewManager.GetView(VIEW_NAME); + Assert.Equal(view, viewData.View); + + var tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(aggregation, measure, values) }, + }, + EPSILON); + } + + + [Fact] + public void GetViewDoesNotClearStats() + { + IView view = CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY }); + viewManager.RegisterView(view); + ITagContext tags = tagger.EmptyBuilder.Put(KEY, VALUE).Build(); + statsRecorder.NewMeasureMap().Put(MEASURE_DOUBLE, 0.1).Record(tags); + IViewData viewData1 = viewManager.GetView(VIEW_NAME); + var tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewData1.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 0.1) }, + }, + EPSILON); + + statsRecorder.NewMeasureMap().Put(MEASURE_DOUBLE, 0.2).Record(tags); + IViewData viewData2 = viewManager.GetView(VIEW_NAME); + + // The second view should have the same start time as the first view, and it should include both + // Recorded values: + + StatsTestUtil.AssertAggregationMapEquals( + viewData2.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 0.1, 0.2) }, + }, + EPSILON); + } + + [Fact] + public void TestRecordCumulativeMultipleTagValues() + { + viewManager.RegisterView( + CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY })); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 10.0) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 30.0) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE_2).Build()); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 50.0) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE_2).Build()); + IViewData viewData = viewManager.GetView(VIEW_NAME); + var tv = TagValues.Create(new List() { VALUE }); + var tv2 = TagValues.Create(new List() { VALUE_2 }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + { tv, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 10.0)}, + { tv2, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 30.0, 50.0)}, + }, + EPSILON); + } + + + // This test checks that MeasureMaper.Record(...) does not throw an exception when no views are + // registered. + [Fact] + public void AllowRecordingWithoutRegisteringMatchingViewData() + { + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 10) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + } + + [Fact] + public void TestRecordWithEmptyStatsContext() + { + viewManager.RegisterView( + CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY })); + // DEFAULT doesn't have tags, but the view has tag key "KEY". + statsRecorder.NewMeasureMap().Put(MEASURE_DOUBLE, 10.0).Record(tagger.Empty); + IViewData viewData = viewManager.GetView(VIEW_NAME); + var tv = TagValues.Create(new List() { MutableViewData.UnknownTagValue }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + // Tag is missing for associated measureValues, should use default tag value + // "unknown/not set". + { tv, + // Should Record stats with default tag value: "KEY" : "unknown/not set". + StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 10.0) + }, + }, + EPSILON); + + } + + [Fact] + public void TestRecord_MeasureNameNotMatch() + { + TestRecord_MeasureNotMatch( + MeasureDouble.Create(MEASURE_NAME, "measure", MEASURE_UNIT), + MeasureDouble.Create(MEASURE_NAME_2, "measure", MEASURE_UNIT), + 10.0); + } + + [Fact] + public void TestRecord_MeasureTypeNotMatch() + { + TestRecord_MeasureNotMatch( + MeasureLong.Create(MEASURE_NAME, "measure", MEASURE_UNIT), + MeasureDouble.Create(MEASURE_NAME, "measure", MEASURE_UNIT), + 10.0); + } + + private void TestRecord_MeasureNotMatch(IMeasure measure1, IMeasure measure2, double value) + { + viewManager.RegisterView(CreateCumulativeView(VIEW_NAME, measure1, MEAN, new List() { KEY })); + ITagContext tags = tagger.EmptyBuilder.Put(KEY, VALUE).Build(); + PutToMeasureMap(statsRecorder.NewMeasureMap(), measure2, value).Record(tags); + IViewData view = viewManager.GetView(VIEW_NAME); + Assert.Empty(view.AggregationMap); + } + + [Fact] + public void TestRecordWithTagsThatDoNotMatchViewData() + { + viewManager.RegisterView( + CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY })); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 10.0) + .Record(tagger.EmptyBuilder.Put(TagKey.Create("wrong key"), VALUE).Build()); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 50.0) + .Record(tagger.EmptyBuilder.Put(TagKey.Create("another wrong key"), VALUE).Build()); + IViewData viewData = viewManager.GetView(VIEW_NAME); + var tv = TagValues.Create(new List() { MutableViewData.UnknownTagValue }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + // Won't Record the unregistered tag key, for missing registered keys will use default + // tag value : "unknown/not set". + { tv, + // Should Record stats with default tag value: "KEY" : "unknown/not set". + StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 10.0, 50.0) }, + }, + EPSILON); + } + + [Fact] + public void TestViewDataWithMultipleTagKeys() + { + ITagKey key1 = TagKey.Create("Key-1"); + ITagKey key2 = TagKey.Create("Key-2"); + viewManager.RegisterView( + CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, new List() { key1, key2 })); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.1) + .Record( + tagger + .EmptyBuilder + .Put(key1, TagValue.Create("v1")) + .Put(key2, TagValue.Create("v10")) + .Build()); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 2.2) + .Record( + tagger + .EmptyBuilder + .Put(key1, TagValue.Create("v1")) + .Put(key2, TagValue.Create("v20")) + .Build()); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 3.3) + .Record( + tagger + .EmptyBuilder + .Put(key1, TagValue.Create("v2")) + .Put(key2, TagValue.Create("v10")) + .Build()); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 4.4) + .Record( + tagger + .EmptyBuilder + .Put(key1, TagValue.Create("v1")) + .Put(key2, TagValue.Create("v10")) + .Build()); + IViewData viewData = viewManager.GetView(VIEW_NAME); + var tv1 = TagValues.Create(new List() { TagValue.Create("v1"), TagValue.Create("v10") }); + var tv2 = TagValues.Create(new List() { TagValue.Create("v1"), TagValue.Create("v20") }); + var tv3 = TagValues.Create(new List() { TagValue.Create("v2"), TagValue.Create("v10") }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + { tv1, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 1.1, 4.4) }, + { tv2, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 2.2) }, + { tv3, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 3.3)}, + }, + EPSILON); + } + + [Fact] + public void TestMultipleViewSameMeasure() + { + IView view1 = + CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY }); + IView view2 = + CreateCumulativeView(VIEW_NAME_2, MEASURE_DOUBLE, DISTRIBUTION, new List() { KEY }); + viewManager.RegisterView(view1); + viewManager.RegisterView(view2); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 5.0) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + IViewData viewData1 = viewManager.GetView(VIEW_NAME); + IViewData viewData2 = viewManager.GetView(VIEW_NAME_2); + var tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewData1.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 5.0) }, + }, + EPSILON); + + StatsTestUtil.AssertAggregationMapEquals( + viewData2.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(DISTRIBUTION, MEASURE_DOUBLE, 5.0) }, + }, + EPSILON); + } + + [Fact] + public void TestMultipleViews_DifferentMeasureNames() + { + TestMultipleViews_DifferentMeasures( + MeasureDouble.Create(MEASURE_NAME, MEASURE_DESCRIPTION, MEASURE_UNIT), + MeasureDouble.Create(MEASURE_NAME_2, MEASURE_DESCRIPTION, MEASURE_UNIT), + 1.1, + 2.2); + } + + [Fact] + public void TestMultipleViews_DifferentMeasureTypes() + { + TestMultipleViews_DifferentMeasures( + MeasureDouble.Create(MEASURE_NAME, MEASURE_DESCRIPTION, MEASURE_UNIT), + MeasureLong.Create(MEASURE_NAME_2, MEASURE_DESCRIPTION, MEASURE_UNIT), + 1.1, + 5000); + } + + private void TestMultipleViews_DifferentMeasures(IMeasure measure1, IMeasure measure2, double value1, double value2) + { + IView view1 = CreateCumulativeView(VIEW_NAME, measure1, DISTRIBUTION, new List() { KEY }); + IView view2 = + CreateCumulativeView(VIEW_NAME_2, measure2, DISTRIBUTION, new List() { KEY }); + viewManager.RegisterView(view1); + viewManager.RegisterView(view2); + ITagContext tags = tagger.EmptyBuilder.Put(KEY, VALUE).Build(); + IMeasureMap measureMap = statsRecorder.NewMeasureMap(); + PutToMeasureMap(measureMap, measure1, value1); + PutToMeasureMap(measureMap, measure2, value2); + measureMap.Record(tags); + IViewData viewData1 = viewManager.GetView(VIEW_NAME); + IViewData viewData2 = viewManager.GetView(VIEW_NAME_2); + var tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewData1.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(DISTRIBUTION, measure1, value1) }, + }, + EPSILON); + + StatsTestUtil.AssertAggregationMapEquals( + viewData2.AggregationMap, + new Dictionary() + { + { tv, StatsTestUtil.CreateAggregationData(DISTRIBUTION, measure2, value2) }, + }, + EPSILON); + } + + [Fact] + public void TestGetCumulativeViewDataWithEmptyBucketBoundaries() + { + IAggregation noHistogram = + Distribution.Create(BucketBoundaries.Create(Enumerable.Empty())); + IView view = CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, noHistogram, new List() { KEY }); + viewManager.RegisterView(view); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.1) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + IViewData viewData = viewManager.GetView(VIEW_NAME); + var tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(noHistogram, MEASURE_DOUBLE, 1.1) }, + }, + EPSILON); + } + + [Fact] + public void TestGetCumulativeViewDataWithoutBucketBoundaries() + { + IView view = CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, new List() { KEY }); + viewManager.RegisterView(view); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.1) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + IViewData viewData = viewManager.GetView(VIEW_NAME); + var tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewData.AggregationMap, + new Dictionary() + { + {tv, StatsTestUtil.CreateAggregationData(MEAN, MEASURE_DOUBLE, 1.1) }, + }, + EPSILON); + } + + [Fact] + public void RegisterRecordAndGetView_StatsDisabled() + { + statsComponent.State = StatsCollectionState.DISABLED; + IView view = CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, new List() { KEY }); + viewManager.RegisterView(view); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.1) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + Assert.Equal(StatsTestUtil.CreateEmptyViewData(view), viewManager.GetView(VIEW_NAME)); + } + + [Fact] + public void RegisterRecordAndGetView_StatsReenabled() + { + statsComponent.State = StatsCollectionState.DISABLED; + statsComponent.State = StatsCollectionState.ENABLED; + IView view = CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, new List() { KEY }); + viewManager.RegisterView(view); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.1) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + TagValues tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewManager.GetView(VIEW_NAME).AggregationMap, + new Dictionary() + { + { tv, StatsTestUtil.CreateAggregationData(MEAN, MEASURE_DOUBLE, 1.1) }, + }, + EPSILON); + } + + [Fact] + public void RegisterViewWithStatsDisabled_RecordAndGetViewWithStatsEnabled() + { + statsComponent.State = StatsCollectionState.DISABLED; + IView view = CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, new List() { KEY }); + viewManager.RegisterView(view); // view will still be registered. + + statsComponent.State = StatsCollectionState.ENABLED; + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.1) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + TagValues tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewManager.GetView(VIEW_NAME).AggregationMap, + new Dictionary() + { + { tv, StatsTestUtil.CreateAggregationData(MEAN, MEASURE_DOUBLE, 1.1) }, + }, + EPSILON); + } + + [Fact] + public void RegisterDifferentViewWithSameNameWithStatsDisabled() + { + statsComponent.State = StatsCollectionState.DISABLED; + IView view1 = + View.Create( + VIEW_NAME, + "View description.", + MEASURE_DOUBLE, + DISTRIBUTION, + new List() { KEY }); + IView view2 = + View.Create( + VIEW_NAME, + "This is a different description.", + MEASURE_DOUBLE, + DISTRIBUTION, + new List() { KEY }); + + TestFailedToRegisterView( + view1, view2, "A different view with the same name is already registered"); + } + + [Fact] + public void SettingStateToDisabledWillClearStats_Cumulative() + { + IView cumulativeView = CreateCumulativeView(VIEW_NAME, MEASURE_DOUBLE, MEAN, new List() { KEY }); + SettingStateToDisabledWillClearStats(cumulativeView); + } + + // [Fact] + // public void SettingStateToDisabledWillClearStats_Interval() + // { + // View intervalView = + // View.Create( + // VIEW_NAME_2, + // VIEW_DESCRIPTION, + // MEASURE_DOUBLE, + // MEAN, + // Arrays.asList(KEY), + // Interval.Create(Duration.Create(60, 0))); + // settingStateToDisabledWillClearStats(intervalView); + // } + + private void SettingStateToDisabledWillClearStats(IView view) + { + // TODO: deal with timestamp validation + Timestamp timestamp1 = Timestamp.Create(1, 0); + //clock.Time = timestamp1; + viewManager.RegisterView(view); + statsRecorder + .NewMeasureMap() + .Put(MEASURE_DOUBLE, 1.1) + .Record(tagger.EmptyBuilder.Put(KEY, VALUE).Build()); + TagValues tv = TagValues.Create(new List() { VALUE }); + StatsTestUtil.AssertAggregationMapEquals( + viewManager.GetView(view.Name).AggregationMap, + new Dictionary() + { + { tv, StatsTestUtil.CreateAggregationData(view.Aggregation, view.Measure, 1.1) }, + }, + EPSILON); + + Timestamp timestamp2 = Timestamp.Create(2, 0); + //clock.Time = timestamp2; + statsComponent.State = StatsCollectionState.DISABLED; // This will clear stats. + Assert.Equal(StatsTestUtil.CreateEmptyViewData(view), viewManager.GetView(view.Name)); + + Timestamp timestamp3 = Timestamp.Create(3, 0); + //clock.Time = timestamp3; + statsComponent.State = StatsCollectionState.ENABLED; + + Timestamp timestamp4 = Timestamp.Create(4, 0); + //clock.Time = timestamp4; + // This ViewData does not have any stats, but it should not be an empty ViewData, since it has + // non-zero TimeStamps. + IViewData viewData = viewManager.GetView(view.Name); + Assert.Empty(viewData.AggregationMap); + //Assert.Equal(timestamp3, viewData.Start); + //Assert.Equal(timestamp4, viewData.End); + // if (windowData instanceof CumulativeData) { + // Assert.Equal(windowData).isEqualTo(CumulativeData.Create(timestamp3, timestamp4)); + // } else { + // Assert.Equal(windowData).isEqualTo(IntervalData.Create(timestamp4)); + // } + } + + private static IMeasureMap PutToMeasureMap(IMeasureMap measureMap, IMeasure measure, double value) + { + if (measure is MeasureDouble) { + return measureMap.Put((IMeasureDouble)measure, value); + } else if (measure is MeasureLong) { + return measureMap.Put((IMeasureLong)measure, (long)Math.Round(value)); + } else { + // Future measures. + throw new Exception(); + } + } + } + +} diff --git a/test/OpenCensus.Tests/Impl/Stats/ViewTest.cs b/test/OpenCensus.Tests/Impl/Stats/ViewTest.cs new file mode 100644 index 000000000..bdb9fe350 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Stats/ViewTest.cs @@ -0,0 +1,131 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Stats.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Stats.Aggregations; + using OpenCensus.Stats.Measures; + using OpenCensus.Tags; + using Xunit; + + public class ViewTest + { + private static readonly IViewName NAME = ViewName.Create("test-view-name"); + private static readonly String DESCRIPTION = "test-view-name description"; + private static readonly IMeasure MEASURE = + MeasureDouble.Create("measure", "measure description", "1"); + + private static readonly ITagKey FOO = TagKey.Create("foo"); + private static readonly ITagKey BAR = TagKey.Create("bar"); + private static readonly List keys = new List() { FOO, BAR }; + private static readonly IMean MEAN = Mean.Create(); + private static readonly Duration MINUTE = Duration.Create(60, 0); + private static readonly Duration TWO_MINUTES = Duration.Create(120, 0); + private static readonly Duration NEG_TEN_SECONDS = Duration.Create(-10, 0); + + [Fact] + public void TestConstants() + { + Assert.Equal(255, ViewName.NameMaxLength); + } + + [Fact] + public void TestDistributionView() + { + IView view = View.Create(NAME, DESCRIPTION, MEASURE, MEAN, keys); + Assert.Equal(NAME, view.Name); + Assert.Equal(DESCRIPTION, view.Description); + Assert.Equal(MEASURE.Name, view.Measure.Name); + Assert.Equal(MEAN, view.Aggregation); + Assert.Equal(2, view.Columns.Count); + Assert.Equal(FOO, view.Columns[0]); + Assert.Equal(BAR, view.Columns[1]); + + } + + [Fact] + public void testViewEquals() + { + + IView view1 = View.Create(NAME, DESCRIPTION, MEASURE, MEAN, keys); + IView view2 = View.Create(NAME, DESCRIPTION, MEASURE, MEAN, keys); + Assert.Equal(view1, view2); + IView view3 = View.Create(NAME, DESCRIPTION + 2, MEASURE, MEAN, keys); + Assert.NotEqual(view1, view3); + Assert.NotEqual(view2, view3); + + } + + [Fact] + public void PreventDuplicateColumns() + { + Assert.Throws(() => View.Create( + NAME, + DESCRIPTION, + MEASURE, + MEAN, + new List() { TagKey.Create("duplicate"), TagKey.Create("duplicate") })); + } + + [Fact] + public void PreventNullViewName() + { + Assert.Throws(() => View.Create(null, DESCRIPTION, MEASURE, MEAN, keys)); + } + + [Fact] + public void PreventTooLongViewName() + { + char[] chars = new char[ViewName.NameMaxLength + 1]; + for (int i = 0; i < chars.Length; i++) + { + chars[i] = 'a'; + } + + String longName = new String(chars); + Assert.Throws(() => ViewName.Create(longName)); + } + + [Fact] + public void PreventNonPrintableViewName() + { + Assert.Throws(() => ViewName.Create("\u0002")); + } + + [Fact] + public void TestViewName() + { + Assert.Equal("my name", ViewName.Create("my name").AsString); + } + + [Fact] + public void PreventNullNameString() + { + Assert.Throws(() => ViewName.Create(null)); + } + + [Fact] + public void TestViewNameEquals() + { + Assert.Equal(ViewName.Create("view-1"), ViewName.Create("view-1")); + Assert.NotEqual(ViewName.Create("view-1"), ViewName.Create("view-2")); + + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/CurrentTagContextUtilsTest.cs b/test/OpenCensus.Tests/Impl/Tags/CurrentTagContextUtilsTest.cs new file mode 100644 index 000000000..ed58b1128 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/CurrentTagContextUtilsTest.cs @@ -0,0 +1,112 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Tags.Unsafe; + using Xunit; + + public class CurrentTagContextUtilsTest + { + private static readonly ITag TAG = Tag.Create(TagKey.Create("key"), TagValue.Create("value")); + + private readonly ITagContext tagContext = new TestTagContext(); + + [Fact] + public void TestGetCurrentTagContext_DefaultContext() + { + ITagContext tags = CurrentTagContextUtils.CurrentTagContext; + Assert.NotNull(tags); + Assert.Empty(TagsTestUtil.TagContextToList(tags)); + } + + [Fact] + public void TestGetCurrentTagContext_ContextSetToNull() + { + var orig = AsyncLocalContext.CurrentTagContext; + AsyncLocalContext.CurrentTagContext = null; + try + { + ITagContext tags = CurrentTagContextUtils.CurrentTagContext; + Assert.NotNull(tags); + Assert.Empty(TagsTestUtil.TagContextToList(tags)); + } + finally + { + AsyncLocalContext.CurrentTagContext = orig; + } + } + + [Fact] + public void TestWithTagContext() + { + Assert.Empty(TagsTestUtil.TagContextToList(CurrentTagContextUtils.CurrentTagContext)); + + IScope scopedTags = CurrentTagContextUtils.WithTagContext(tagContext); + try + { + Assert.Same(tagContext, CurrentTagContextUtils.CurrentTagContext); + } + finally + { + scopedTags.Dispose(); + } + Assert.Empty(TagsTestUtil.TagContextToList(CurrentTagContextUtils.CurrentTagContext)); + } + + [Fact] + public void TestWithTagContextUsingWrap() + { + // Runnable runnable; + // Scope scopedTags = CurrentTagContextUtils.withTagContext(tagContext); + // try + // { + // assertThat(CurrentTagContextUtils.getCurrentTagContext()).isSameAs(tagContext); + // runnable = + // Context.current() + // .wrap( + // new Runnable() { + // @Override + // public void run() + // { + // assertThat(CurrentTagContextUtils.getCurrentTagContext()) + // .isSameAs(tagContext); + // } + // }); + // } finally { + // scopedTags.close(); + // } + // assertThat(tagContextToList(CurrentTagContextUtils.getCurrentTagContext())).isEmpty(); + //// When we run the runnable we will have the TagContext in the current Context. + // runnable.run(); + } + + class TestTagContext : TagContextBase + { + public TestTagContext() + { + + } + + public override IEnumerator GetEnumerator() + { + return new List() { TAG }.GetEnumerator(); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/CurrentTaggingStateTest.cs b/test/OpenCensus.Tests/Impl/Tags/CurrentTaggingStateTest.cs new file mode 100644 index 000000000..7a7a4d2b8 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/CurrentTaggingStateTest.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System; + using Xunit; + + public class CurrentTaggingStateTest + { + [Fact] + public void DefaultState() + { + Assert.Equal(TaggingState.ENABLED, new CurrentTaggingState().Value); + } + + [Fact] + public void SetState() + { + CurrentTaggingState state = new CurrentTaggingState(); + state.Set(TaggingState.DISABLED); + Assert.Equal(TaggingState.DISABLED, state.Internal); + state.Set(TaggingState.ENABLED); + Assert.Equal(TaggingState.ENABLED, state.Internal); + } + + + [Fact] + public void PreventSettingStateAfterReadingState() + { + CurrentTaggingState state = new CurrentTaggingState(); + var current = state.Value; + Assert.Throws(() => state.Set(TaggingState.DISABLED)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/NoopTagsTest.cs b/test/OpenCensus.Tests/Impl/Tags/NoopTagsTest.cs new file mode 100644 index 000000000..c4f266a30 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/NoopTagsTest.cs @@ -0,0 +1,146 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Internal; + using OpenCensus.Tags.Propagation; + using Xunit; + + public class NoopTagsTest + { + private static readonly ITagKey KEY = TagKey.Create("key"); + private static readonly ITagValue VALUE = TagValue.Create("value"); + + private static readonly ITagContext TAG_CONTEXT = new TestTagContext(); + + + + [Fact] + public void NoopTagsComponent() + { + Assert.Same(NoopTags.NoopTagger, NoopTags.NewNoopTagsComponent().Tagger); + Assert.Equal(NoopTags.NoopTagPropagationComponent, NoopTags.NewNoopTagsComponent().TagPropagationComponent); + } + + [Fact] + public void NoopTagger() + { + ITagger noopTagger = NoopTags.NoopTagger; + Assert.Same(NoopTags.NoopTagContext, noopTagger.Empty); + Assert.Same(NoopTags.NoopTagContext, noopTagger.CurrentTagContext); + Assert.Same(NoopTags.NoopTagContextBuilder, noopTagger.EmptyBuilder); + Assert.Same(NoopTags.NoopTagContextBuilder, noopTagger.ToBuilder(TAG_CONTEXT)); + Assert.Same(NoopTags.NoopTagContextBuilder, noopTagger.CurrentBuilder); + Assert.Same(NoopScope.Instance, noopTagger.WithTagContext(TAG_CONTEXT)); + } + + [Fact] + public void NoopTagger_ToBuilder_DisallowsNull() + { + ITagger noopTagger = NoopTags.NoopTagger; + Assert.Throws(() => noopTagger.ToBuilder(null)); + } + + [Fact] + public void NoopTagger_WithTagContext_DisallowsNull() + { + ITagger noopTagger = NoopTags.NoopTagger; + Assert.Throws(() => noopTagger.WithTagContext(null)); + } + + [Fact] + public void NoopTagContextBuilder() + { + Assert.Same(NoopTags.NoopTagContext, NoopTags.NoopTagContextBuilder.Build()); + Assert.Same(NoopTags.NoopTagContext, NoopTags.NoopTagContextBuilder.Put(KEY, VALUE).Build()); + Assert.Same(NoopScope.Instance, NoopTags.NoopTagContextBuilder.BuildScoped()); + Assert.Same(NoopScope.Instance, NoopTags.NoopTagContextBuilder.Put(KEY, VALUE).BuildScoped()); + } + + [Fact] + public void NoopTagContextBuilder_Put_DisallowsNullKey() + { + ITagContextBuilder noopBuilder = NoopTags.NoopTagContextBuilder; + Assert.Throws(() => noopBuilder.Put(null, VALUE)); + } + + [Fact] + public void NoopTagContextBuilder_Put_DisallowsNullValue() + { + ITagContextBuilder noopBuilder = NoopTags.NoopTagContextBuilder; + Assert.Throws(() => noopBuilder.Put(KEY, null)); + } + + [Fact] + public void NoopTagContextBuilder_Remove_DisallowsNullKey() + { + ITagContextBuilder noopBuilder = NoopTags.NoopTagContextBuilder; + Assert.Throws(() => noopBuilder.Remove(null)); + } + + [Fact] + public void NoopTagContext() + { + Assert.Empty(NoopTags.NoopTagContext.ToList()); + } + + [Fact] + public void NoopTagPropagationComponent() + { + Assert.Same(NoopTags.NoopTagContextBinarySerializer, NoopTags.NoopTagPropagationComponent.BinarySerializer); + } + + [Fact] + public void NoopTagContextBinarySerializer() + { + Assert.Equal(new byte[0], NoopTags.NoopTagContextBinarySerializer.ToByteArray(TAG_CONTEXT)); + Assert.Equal(NoopTags.NoopTagContext, NoopTags.NoopTagContextBinarySerializer.FromByteArray(new byte[5])); + } + + [Fact] + public void NoopTagContextBinarySerializer_ToByteArray_DisallowsNull() + { + ITagContextBinarySerializer noopSerializer = NoopTags.NoopTagContextBinarySerializer; + Assert.Throws(() => noopSerializer.ToByteArray(null)); + } + + [Fact] + public void NoopTagContextBinarySerializer_FromByteArray_DisallowsNull() + { + ITagContextBinarySerializer noopSerializer = NoopTags.NoopTagContextBinarySerializer; + Assert.Throws(() => noopSerializer.FromByteArray(null)); + } + + class TestTagContext : ITagContext + { + public IEnumerator GetEnumerator() + { + var list = new List() { Tag.Create(KEY, VALUE) }; + return list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextBinarySerializerTest.cs b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextBinarySerializerTest.cs new file mode 100644 index 000000000..28c9765e8 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextBinarySerializerTest.cs @@ -0,0 +1,88 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation.Test +{ + using System.Collections.Generic; + + public class TagContextBinarySerializerTest + { + private readonly TagsComponent tagsComponent = new TagsComponent(); + private readonly ITagContextBinarySerializer serializer; + + private readonly ITagContext tagContext = new TestTagContext(); + // new TagContext() + // { + // @Override + // public Iterator getIterator() + // { + // return ImmutableSet.< Tag > of(Tag.create(TagKey.create("key"), TagValue.create("value"))) + // .iterator(); + // } + // }; + + public TagContextBinarySerializerTest() + { + serializer = tagsComponent.TagPropagationComponent.BinarySerializer; + } + // [Fact] + // public void ToByteArray_TaggingDisabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // assertThat(serializer.toByteArray(tagContext)).isEmpty(); + // } + + // [Fact] + // public void ToByteArray_TaggingReenabled() + // { + // byte[] serialized = serializer.ToByteArray(tagContext); + // tagsComponent.setState(TaggingState.DISABLED); + // assertThat(serializer.toByteArray(tagContext)).isEmpty(); + // tagsComponent.setState(TaggingState.ENABLED); + // assertThat(serializer.toByteArray(tagContext)).isEqualTo(serialized); + // } + + // [Fact] + // public void FromByteArray_TaggingDisabled() + // { + // byte[] serialized = serializer.toByteArray(tagContext); + // tagsComponent.setState(TaggingState.DISABLED); + // assertThat(TagsTestUtil.tagContextToList(serializer.fromByteArray(serialized))).isEmpty(); + // } + + // [Fact] + // public void FromByteArray_TaggingReenabled() + // { + // byte[] serialized = serializer.toByteArray(tagContext); + // tagsComponent.setState(TaggingState.DISABLED); + // assertThat(TagsTestUtil.tagContextToList(serializer.fromByteArray(serialized))).isEmpty(); + // tagsComponent.setState(TaggingState.ENABLED); + // assertThat(serializer.fromByteArray(serialized)).isEqualTo(tagContext); + // } + class TestTagContext : TagContextBase + { + public TestTagContext() + { + + } + + public override IEnumerator GetEnumerator() + { + return new List() { Tag.Create(TagKey.Create("key"), TagValue.Create("value")) }.GetEnumerator(); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationExceptionTest.cs b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationExceptionTest.cs new file mode 100644 index 000000000..038a8bccb --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationExceptionTest.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation.Test +{ + using System; + using Xunit; + + public class TagContextDeserializationExceptionTest + { + [Fact] + public void CreateWithMessage() + { + Assert.Equal("my message", new TagContextDeserializationException("my message").Message); + } + + [Fact] + public void CreateWithMessageAndCause() + { + Exception cause = new Exception(); + TagContextDeserializationException exception = new TagContextDeserializationException("my message", cause); + Assert.Equal("my message", exception.Message); + Assert.Equal(cause, exception.InnerException); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationTest.cs b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationTest.cs new file mode 100644 index 000000000..8bf9d9f15 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextDeserializationTest.cs @@ -0,0 +1,332 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation.Test +{ + using System; + using System.IO; + using System.Text; + using OpenCensus.Internal; + using Xunit; + + public class TagContextDeserializationTest + { + private readonly TagsComponent tagsComponent = new TagsComponent(); + private readonly ITagContextBinarySerializer serializer; + private readonly ITagger tagger; + + public TagContextDeserializationTest() + { + serializer = tagsComponent.TagPropagationComponent.BinarySerializer; + tagger = tagsComponent.Tagger; + } + + [Fact] + public void TestConstants() + { + // Refer to the JavaDoc on SerializationUtils for the definitions on these constants. + Assert.Equal(0, SerializationUtils.VersionId); + Assert.Equal(0, SerializationUtils.TagFieldId); + Assert.Equal(8192, SerializationUtils.TagContextSerializedSizeLimit); + } + + [Fact] + public void TestDeserializeNoTags() + { + ITagContext expected = tagger.Empty; + ITagContext actual = serializer.FromByteArray(new byte[] { SerializationUtils.VersionId }); // One byte that represents Version ID. + Assert.Equal(expected, actual); + } + + [Fact] + public void TestDeserializeEmptyByteArrayThrowException() + { + Assert.Throws(() => serializer.FromByteArray(new byte[0])); + } + + [Fact] + public void TestDeserializeTooLargeByteArrayThrowException() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + for (int i = 0; i < SerializationUtils.TagContextSerializedSizeLimit / 8 - 1; i++) { + // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. + String str; + if (i < 10) + { + str = "000" + i; + } + else if (i < 100) + { + str = "00" + i; + } + else if (i < 1000) + { + str = "0" + i; + } + else + { + str = i.ToString(); + } + EncodeTagToOutPut(str, str, output); + } + // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte + // more than limit. + EncodeTagToOutPut("last", "last1", output); + + byte[] bytes = output.ToArray(); + + Assert.Throws(() => serializer.FromByteArray(bytes)); + } + + // Deserializing this inPut should cause an error, even though it represents a relatively small + // TagContext. + [Fact] + public void TestDeserializeTooLargeByteArrayThrowException_WithDuplicateTagKeys() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + for (int i = 0; i < SerializationUtils.TagContextSerializedSizeLimit / 8 - 1; i++) { + // Each tag will be with format {key : "key_", value : "0123"}, so the length of it is 8. + String str; + if (i < 10) + { + str = "000" + i; + } + else if (i < 100) + { + str = "00" + i; + } + else if (i < 1000) + { + str = "0" + i; + } + else + { + str = i.ToString(); + } + EncodeTagToOutPut("key_", str, output); + } + // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte + // more than limit. + EncodeTagToOutPut("key_", "last1", output); + + byte[] bytes = output.ToArray(); + + Assert.Throws(() => serializer.FromByteArray(bytes)); + } + + [Fact] + public void TestDeserializeInvalidTagKey() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + + // Encode an invalid tag key and a valid tag value: + EncodeTagToOutPut("\u0002key", "value", output); + byte[] bytes = output.ToArray(); + + + Assert.Throws(() => serializer.FromByteArray(bytes)); + } + + [Fact] + public void TestDeserializeInvalidTagValue() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + + // Encode a valid tag key and an invalid tag value: + EncodeTagToOutPut("my key", "val\u0003", output); + byte[] bytes = output.ToArray(); + + + Assert.Throws(() => serializer.FromByteArray(bytes)); + } + + [Fact] + public void TestDeserializeOneTag() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + EncodeTagToOutPut("Key", "Value", output); + ITagContext expected = tagger.EmptyBuilder.Put(TagKey.Create("Key"), TagValue.Create("Value")).Build(); + Assert.Equal(expected, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void TestDeserializeMultipleTags() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + EncodeTagToOutPut("Key1", "Value1", output); + EncodeTagToOutPut("Key2", "Value2", output); + ITagContext expected = + tagger + .EmptyBuilder + .Put(TagKey.Create("Key1"), TagValue.Create("Value1")) + .Put(TagKey.Create("Key2"), TagValue.Create("Value2")) + .Build(); + Assert.Equal(expected, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void TestDeserializeDuplicateKeys() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + EncodeTagToOutPut("Key1", "Value1", output); + EncodeTagToOutPut("Key1", "Value2", output); + ITagContext expected = + tagger.EmptyBuilder.Put(TagKey.Create("Key1"), TagValue.Create("Value2")).Build(); + + Assert.Equal(expected, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void TestDeserializeNonConsecutiveDuplicateKeys() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + EncodeTagToOutPut("Key1", "Value1", output); + EncodeTagToOutPut("Key2", "Value2", output); + EncodeTagToOutPut("Key3", "Value3", output); + EncodeTagToOutPut("Key1", "Value4", output); + EncodeTagToOutPut("Key2", "Value5", output); + ITagContext expected = + tagger + .EmptyBuilder + .Put(TagKey.Create("Key1"), TagValue.Create("Value4")) + .Put(TagKey.Create("Key2"), TagValue.Create("Value5")) + .Put(TagKey.Create("Key3"), TagValue.Create("Value3")) + .Build(); + Assert.Equal(expected, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void TestDeserializeDuplicateTags() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + EncodeTagToOutPut("Key1", "Value1", output); + EncodeTagToOutPut("Key1", "Value1", output); + ITagContext expected = + tagger.EmptyBuilder.Put(TagKey.Create("Key1"), TagValue.Create("Value1")).Build(); + Assert.Equal(expected, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void TestDeserializeNonConsecutiveDuplicateTags() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + EncodeTagToOutPut("Key1", "Value1", output); + EncodeTagToOutPut("Key2", "Value2", output); + EncodeTagToOutPut("Key3", "Value3", output); + EncodeTagToOutPut("Key1", "Value1", output); + EncodeTagToOutPut("Key2", "Value2", output); + ITagContext expected = + tagger + .EmptyBuilder + .Put(TagKey.Create("Key1"), TagValue.Create("Value1")) + .Put(TagKey.Create("Key2"), TagValue.Create("Value2")) + .Put(TagKey.Create("Key3"), TagValue.Create("Value3")) + .Build(); + Assert.Equal(expected, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void StopParsingAtUnknownField() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + EncodeTagToOutPut("Key1", "Value1", output); + EncodeTagToOutPut("Key2", "Value2", output); + + // Write unknown field ID 1. + output.WriteByte(1); + output.Write(new byte[] { 1, 2, 3, 4 }, 0, 4); + + EncodeTagToOutPut("Key3", "Value3", output); + + // key 3 should not be included + ITagContext expected = + tagger + .EmptyBuilder + .Put(TagKey.Create("Key1"), TagValue.Create("Value1")) + .Put(TagKey.Create("Key2"), TagValue.Create("Value2")) + .Build(); + Assert.Equal(expected, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void StopParsingAtUnknownTagAtStart() + { + MemoryStream output = new MemoryStream(); + output.WriteByte(SerializationUtils.VersionId); + + // Write unknown field ID 1. + output.WriteByte(1); + output.Write(new byte[] { 1, 2, 3, 4 }, 0, 4); + + EncodeTagToOutPut("Key", "Value", output); + Assert.Equal(tagger.Empty, serializer.FromByteArray(output.ToArray())); + } + + [Fact] + public void TestDeserializeWrongFormat() + { + // encoded tags should follow the format ()* + Assert.Throws(() => serializer.FromByteArray(new byte[3])); + } + + [Fact] + public void TestDeserializeWrongVersionId() + { + + Assert.Throws(() => serializer.FromByteArray(new byte[] { SerializationUtils.VersionId + 1 })); + } + + [Fact] + public void TestDeserializeNegativeVersionId() + { + Assert.Throws(() => serializer.FromByteArray(new byte[] { 0xff })); + } + + // == + // + // == varint encoded integer + // == tag_key_len bytes comprising tag key name + // == varint encoded integer + // == tag_val_len bytes comprising UTF-8 string + private static void EncodeTagToOutPut(String key, String value, MemoryStream output) + { + output.WriteByte(SerializationUtils.TagFieldId); + EncodeString(key, output); + EncodeString(value, output); + } + + private static void EncodeString(String input, MemoryStream output) + { + int length = input.Length; + byte[] bytes = new byte[VarInt.VarIntSize(length)]; + VarInt.PutVarInt(length, bytes, 0); + output.Write(bytes, 0, bytes.Length); + byte[] inPutBytes = Encoding.UTF8.GetBytes(input); + output.Write(inPutBytes, 0, inPutBytes.Length); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextRoundtripTest.cs b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextRoundtripTest.cs new file mode 100644 index 000000000..ca60658b9 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextRoundtripTest.cs @@ -0,0 +1,90 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation.Test +{ + using System; + using Xunit; + + public class TagContextRoundtripTest + { + + private static readonly ITagKey K1 = TagKey.Create("k1"); + private static readonly ITagKey K2 = TagKey.Create("k2"); + private static readonly ITagKey K3 = TagKey.Create("k3"); + + private static readonly ITagValue V_EMPTY = TagValue.Create(""); + private static readonly ITagValue V1 = TagValue.Create("v1"); + private static readonly ITagValue V2 = TagValue.Create("v2"); + private static readonly ITagValue V3 = TagValue.Create("v3"); + + private readonly TagsComponent tagsComponent = new TagsComponent(); + private readonly ITagContextBinarySerializer serializer; + private readonly ITagger tagger; + + public TagContextRoundtripTest() + { + serializer = tagsComponent.TagPropagationComponent.BinarySerializer; + tagger = tagsComponent.Tagger; + } + + [Fact] + public void TestRoundtripSerialization_NormalTagContext() + { + TestRoundtripSerialization(tagger.Empty); + TestRoundtripSerialization(tagger.EmptyBuilder.Put(K1, V1).Build()); + TestRoundtripSerialization(tagger.EmptyBuilder.Put(K1, V1).Put(K2, V2).Put(K3, V3).Build()); + TestRoundtripSerialization(tagger.EmptyBuilder.Put(K1, V_EMPTY).Build()); + } + + [Fact] + public void TestRoundtrip_TagContextWithMaximumSize() + { + ITagContextBuilder builder = tagger.EmptyBuilder; + for (int i = 0; i < SerializationUtils.TagContextSerializedSizeLimit / 8; i++) + { + // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. + // Add 1024 tags, the total size should just be 8192. + String str; + if (i < 10) + { + str = "000" + i; + } + else if (i < 100) + { + str = "00" + i; + } + else if (i < 1000) + { + str = "0" + i; + } + else + { + str = "" + i; + } + builder.Put(TagKey.Create(str), TagValue.Create(str)); + } + TestRoundtripSerialization(builder.Build()); + } + + private void TestRoundtripSerialization(ITagContext expected) + { + byte[] bytes = serializer.ToByteArray(expected); + ITagContext actual = serializer.FromByteArray(bytes); + Assert.Equal(expected, actual); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationExceptionTest.cs b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationExceptionTest.cs new file mode 100644 index 000000000..9fd023c36 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationExceptionTest.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation.Test +{ + using System; + using Xunit; + + public class TagContextSerializationExceptionTest + { + [Fact] + public void CreateWithMessage() + { + Assert.Equal("my message", new TagContextSerializationException("my message").Message); + } + + [Fact] + public void CreateWithMessageAndCause() + { + Exception cause = new Exception(); + TagContextSerializationException exception = new TagContextSerializationException("my message", cause); + Assert.Equal("my message", exception.Message); + Assert.Equal(cause, exception.InnerException); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationTest.cs b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationTest.cs new file mode 100644 index 000000000..47bf4f035 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/Propagation/TagContextSerializationTest.cs @@ -0,0 +1,168 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Propagation.Test +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using OpenCensus.Internal; + using Xunit; + + public class TagContextSerializationTest + { + private static readonly ITagKey K1 = TagKey.Create("k1"); + private static readonly ITagKey K2 = TagKey.Create("k2"); + private static readonly ITagKey K3 = TagKey.Create("k3"); + private static readonly ITagKey K4 = TagKey.Create("k4"); + + private static readonly ITagValue V1 = TagValue.Create("v1"); + private static readonly ITagValue V2 = TagValue.Create("v2"); + private static readonly ITagValue V3 = TagValue.Create("v3"); + private static readonly ITagValue V4 = TagValue.Create("v4"); + + private static readonly ITag T1 = Tag.Create(K1, V1); + private static readonly ITag T2 = Tag.Create(K2, V2); + private static readonly ITag T3 = Tag.Create(K3, V3); + private static readonly ITag T4 = Tag.Create(K4, V4); + + private readonly TagsComponent tagsComponent = new TagsComponent(); + private readonly ITagContextBinarySerializer serializer; + private readonly ITagger tagger; + + public TagContextSerializationTest() + { + serializer = tagsComponent.TagPropagationComponent.BinarySerializer; + tagger = tagsComponent.Tagger; + } + + [Fact] + public void TestSerializeDefault() + { + TestSerialize(); + } + + [Fact] + public void TestSerializeWithOneTag() + { + TestSerialize(T1); + } + + [Fact] + public void TestSerializeWithMultipleTags() + { + TestSerialize(T1, T2, T3, T4); + } + + [Fact] + public void TestSerializeTooLargeTagContext() + { + ITagContextBuilder builder = tagger.EmptyBuilder; + for (int i = 0; i < SerializationUtils.TagContextSerializedSizeLimit / 8 - 1; i++) { + // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. + String str; + if (i < 10) + { + str = "000" + i; + } + else if (i < 100) + { + str = "00" + i; + } + else if (i < 1000) + { + str = "0" + i; + } + else + { + str = i.ToString(); + } + builder.Put(TagKey.Create(str), TagValue.Create(str)); + } + // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte + // more than limit. + builder.Put(TagKey.Create("last"), TagValue.Create("last1")); + + ITagContext tagContext = builder.Build(); + + Assert.Throws(() => serializer.ToByteArray(tagContext)); + } + + private void TestSerialize(params ITag[] tags) + { + ITagContextBuilder builder = tagger.EmptyBuilder; + foreach (var tag in tags) + { + builder.Put(tag.Key, tag.Value); + } + + byte[] actual = serializer.ToByteArray(builder.Build()); + var tagsList = tags.ToList(); + var tagPermutation = Permutate(tagsList, tagsList.Count); + ISet possibleOutPuts = new HashSet(); + foreach (List list in tagPermutation) { + MemoryStream expected = new MemoryStream(); + expected.WriteByte(SerializationUtils.VersionId); + foreach (ITag tag in list) { + expected.WriteByte(SerializationUtils.TagFieldId); + EncodeString(tag.Key.Name, expected); + EncodeString(tag.Value.AsString, expected); + } + var bytes = expected.ToArray(); + possibleOutPuts.Add(Encoding.UTF8.GetString(bytes)); + } + var exp = Encoding.UTF8.GetString(actual); + Assert.Contains(exp, possibleOutPuts); + } + + private static void EncodeString(String input, MemoryStream byteArrayOutPutStream) + { + VarInt.PutVarInt(input.Length, byteArrayOutPutStream); + var inpBytes = Encoding.UTF8.GetBytes(input); + byteArrayOutPutStream.Write(inpBytes, 0, inpBytes.Length); + } + + internal static void RotateRight(IList sequence, int count) + { + object tmp = sequence[count - 1]; + sequence.RemoveAt(count - 1); + sequence.Insert(0, tmp); + } + + internal static IEnumerable Permutate(IList sequence, int count) + { + if (count == 0) + { + yield return sequence; + } + else + { + for (int i = 0; i < count; i++) + { + foreach (var perm in Permutate(sequence, count - 1)) + { + yield return perm; + } + + RotateRight(sequence, count); + } + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/ScopedTagContextsTest.cs b/test/OpenCensus.Tests/Impl/Tags/ScopedTagContextsTest.cs new file mode 100644 index 000000000..8fc37eb59 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/ScopedTagContextsTest.cs @@ -0,0 +1,118 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System.Collections.Generic; + using OpenCensus.Common; + using Xunit; + + public class ScopedTagContextsTest + { + private static readonly ITagKey KEY_1 = TagKey.Create("key 1"); + private static readonly ITagKey KEY_2 = TagKey.Create("key 2"); + + private static readonly ITagValue VALUE_1 = TagValue.Create("value 1"); + private static readonly ITagValue VALUE_2 = TagValue.Create("value 2"); + + private readonly ITagger tagger = new Tagger(new CurrentTaggingState()); + + [Fact] + public void DefaultTagContext() + { + ITagContext defaultTagContext = tagger.CurrentTagContext; + Assert.Empty(TagsTestUtil.TagContextToList(defaultTagContext)); + Assert.IsType(defaultTagContext); + } + + [Fact] + public void WithTagContext() + { + Assert.Empty(TagsTestUtil.TagContextToList(tagger.CurrentTagContext)); + ITagContext scopedTags = tagger.EmptyBuilder.Put(KEY_1, VALUE_1).Build(); + IScope scope = tagger.WithTagContext(scopedTags); + try + { + Assert.Same(scopedTags, tagger.CurrentTagContext); + } + finally + { + scope.Dispose(); + } + Assert.Empty(TagsTestUtil.TagContextToList(tagger.CurrentTagContext)); + } + + [Fact] + public void CreateBuilderFromCurrentTags() + { + ITagContext scopedTags = tagger.EmptyBuilder.Put(KEY_1, VALUE_1).Build(); + IScope scope = tagger.WithTagContext(scopedTags); + try + { + ITagContext newTags = tagger.CurrentBuilder.Put(KEY_2, VALUE_2).Build(); + Assert.Equal(new List() { Tag.Create(KEY_1, VALUE_1), Tag.Create(KEY_2, VALUE_2) }, + TagsTestUtil.TagContextToList(newTags)); + Assert.Same(scopedTags, tagger.CurrentTagContext); + } + finally + { + scope.Dispose(); + } + } + + [Fact] + public void SetCurrentTagsWithBuilder() + { + Assert.Empty(TagsTestUtil.TagContextToList(tagger.CurrentTagContext)); + IScope scope = tagger.EmptyBuilder.Put(KEY_1, VALUE_1).BuildScoped(); + try + { + Assert.Equal(new List() { Tag.Create(KEY_1, VALUE_1) }, TagsTestUtil.TagContextToList(tagger.CurrentTagContext)); + } + finally + { + scope.Dispose(); + } + Assert.Empty(TagsTestUtil.TagContextToList(tagger.CurrentTagContext)); + } + + [Fact] + public void AddToCurrentTagsWithBuilder() + { + ITagContext scopedTags = tagger.EmptyBuilder.Put(KEY_1, VALUE_1).Build(); + IScope scope1 = tagger.WithTagContext(scopedTags); + try + { + IScope scope2 = tagger.CurrentBuilder.Put(KEY_2, VALUE_2).BuildScoped(); + try + { + Assert.Equal(new List() { Tag.Create(KEY_1, VALUE_1), Tag.Create(KEY_2, VALUE_2) }, + TagsTestUtil.TagContextToList(tagger.CurrentTagContext)); + } + finally + { + scope2.Dispose(); + } + Assert.Same(scopedTags, tagger.CurrentTagContext); + } + finally + { + scope1.Dispose(); + } + } + } +} + diff --git a/test/OpenCensus.Tests/Impl/Tags/TagContextBaseTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagContextBaseTest.cs new file mode 100644 index 000000000..f7b0f900a --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagContextBaseTest.cs @@ -0,0 +1,118 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System.Collections.Generic; + using Xunit; + + public class TagContextBaseTest + { + private static readonly ITag TAG1 = Tag.Create(TagKey.Create("key"), TagValue.Create("val")); + private static readonly ITag TAG2 = Tag.Create(TagKey.Create("key2"), TagValue.Create("val")); + + [Fact] + public void Equals_IgnoresTagOrderAndTagContextClass() + { + var ctx1 = new SimpleTagContext(TAG1, TAG2); + var ctx2 = new SimpleTagContext(TAG1, TAG2); + var ctx3 = new SimpleTagContext(TAG2, TAG1); + var ctx4 = new TestTagContext(); + + Assert.True(ctx1.Equals(ctx2)); + Assert.True(ctx1.Equals(ctx3)); + Assert.True(ctx1.Equals(ctx4)); + Assert.True(ctx2.Equals(ctx3)); + Assert.True(ctx2.Equals(ctx4)); + Assert.True(ctx3.Equals(ctx4)); + } + + [Fact] + public void Equals_HandlesNullIterator() + { + var ctx1 = new SimpleTagContext((IEnumerable)null); + var ctx2 = new SimpleTagContext((IEnumerable)null); + var ctx3 = new SimpleTagContext(); + Assert.True(ctx1.Equals(ctx2)); + Assert.True(ctx1.Equals(ctx3)); + Assert.True(ctx2.Equals(ctx3)); + } + + [Fact] + public void Equals_DoesNotIgnoreNullTags() + { + var ctx1 = new SimpleTagContext(TAG1); + var ctx2 = new SimpleTagContext(TAG1, null); + var ctx3 = new SimpleTagContext(null, TAG1); + var ctx4 = new SimpleTagContext(TAG1, null, null); + + Assert.True(ctx2.Equals(ctx3)); + Assert.False(ctx1.Equals(ctx2)); + Assert.False(ctx1.Equals(ctx3)); + Assert.False(ctx1.Equals(ctx4)); + Assert.False(ctx2.Equals(ctx4)); + Assert.False(ctx3.Equals(ctx4)); + } + + [Fact] + public void Equals_DoesNotIgnoreDuplicateTags() + { + var ctx1 = new SimpleTagContext(TAG1); + var ctx2 = new SimpleTagContext(TAG1, TAG1); + Assert.True(ctx1.Equals(ctx1)); + Assert.True(ctx2.Equals(ctx2)); + Assert.False(ctx1.Equals(ctx2)); + } + + [Fact] + public void TestToString() + { + Assert.Equal("TagContext", new SimpleTagContext().ToString()); + Assert.Equal("TagContext", new SimpleTagContext(TAG1, TAG2).ToString()); + } + + class TestTagContext : TagContextBase + { + public override IEnumerator GetEnumerator() + { + var l = new List() { TAG1, TAG2 }; + return l.GetEnumerator(); + } + } + + class SimpleTagContext : TagContextBase + { + private readonly IEnumerable tags; + + // This Error Prone warning doesn't seem correct, because the constructor is just calling + // another constructor. + public SimpleTagContext(params ITag[] tags) + : this(new List(tags)) + { + } + + public SimpleTagContext(IEnumerable tags) + { + this.tags = tags == null ? null : new List(tags); + } + + public override IEnumerator GetEnumerator() + { + return tags == null ? null : tags.GetEnumerator(); + } + } + } +} \ No newline at end of file diff --git a/test/OpenCensus.Tests/Impl/Tags/TagContextTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagContextTest.cs new file mode 100644 index 000000000..757f12130 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagContextTest.cs @@ -0,0 +1,154 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System; + using System.Collections.Generic; + using Xunit; + + public class TagContextTest + { + private readonly ITagger tagger = new Tagger(new CurrentTaggingState()); + + private static readonly ITagKey K1 = TagKey.Create("k1"); + private static readonly ITagKey K2 = TagKey.Create("k2"); + + private static readonly ITagValue V1 = TagValue.Create("v1"); + private static readonly ITagValue V2 = TagValue.Create("v2"); + + + [Fact] + public void getTags_empty() + { + TagContext tags = new TagContext(new Dictionary()); + Assert.Empty(tags.Tags); + } + + [Fact] + public void getTags_nonEmpty() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 }, { K2, V2 } }); + Assert.Equal(new Dictionary() { { K1, V1 }, { K2, V2 } }, tags.Tags); + } + + [Fact] + public void Put_NewKey() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 } }); + Assert.Equal(new Dictionary() { { K1, V1 }, { K2, V2 } }, + ((TagContext)tagger.ToBuilder(tags).Put(K2, V2).Build()).Tags); + } + + [Fact] + public void Put_ExistingKey() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 } }); + Assert.Equal(new Dictionary() { { K1, V2 } }, + ((TagContext)tagger.ToBuilder(tags).Put(K1, V2).Build()).Tags); + } + + [Fact] + public void Put_NullKey() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 } }); + ITagContextBuilder builder = tagger.ToBuilder(tags); + Assert.Throws(() => builder.Put(null, V2)); + } + + [Fact] + public void Put_NullValue() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 } }); + ITagContextBuilder builder = tagger.ToBuilder(tags); + Assert.Throws(() => builder.Put(K2, null)); + } + + [Fact] + public void Remove_ExistingKey() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 }, { K2, V2 } }); + Assert.Equal(new Dictionary() { { K2, V2 } }, ((TagContext)tagger.ToBuilder(tags).Remove(K1).Build()).Tags); + } + + [Fact] + public void Remove_DifferentKey() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 } }); + Assert.Equal(new Dictionary() { { K1, V1 } }, ((TagContext)tagger.ToBuilder(tags).Remove(K2).Build()).Tags); + } + + [Fact] + public void Remove_NullKey() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 } }); + ITagContextBuilder builder = tagger.ToBuilder(tags); + Assert.Throws(() => builder.Remove(null)); + } + + [Fact] + public void TestIterator() + { + TagContext tags = new TagContext(new Dictionary() { { K1, V1 }, { K2, V2 } }); + var i = tags.GetEnumerator(); + Assert.True(i.MoveNext()); + ITag tag1 = i.Current; + Assert.True(i.MoveNext()); + ITag tag2 = i.Current; + Assert.False(i.MoveNext()); + Assert.Equal(new List() { Tag.Create(K1, V1), Tag.Create(K2, V2)}, new List() { tag1, tag2 }); + + } + + + + [Fact] + public void TestEquals() + { + // new EqualsTester() + // .addEqualityGroup( + var t1 = tagger.EmptyBuilder.Put(K1, V1).Put(K2, V2).Build(); + var t2 = tagger.EmptyBuilder.Put(K1, V1).Put(K2, V2).Build(); + var t3 = tagger.EmptyBuilder.Put(K2, V2).Put(K1, V1).Build(); + var t4 = new TestTagContext(); + + + var t5 = tagger.EmptyBuilder.Put(K1, V1).Put(K2, V1).Build(); + var t6 = tagger.EmptyBuilder.Put(K1, V2).Put(K2, V1).Build(); + + Assert.True(t1.Equals(t2)); + Assert.True(t1.Equals(t3)); + Assert.True(t1.Equals(t4)); + + Assert.False(t1.Equals(t5)); + Assert.False(t2.Equals(t5)); + Assert.False(t3.Equals(t5)); + Assert.False(t4.Equals(t5)); + Assert.False(t6.Equals(t5)); + + Assert.False(t5.Equals(t6)); + + } + + class TestTagContext : TagContextBase + { + public override IEnumerator GetEnumerator() + { + return new List() { Tag.Create(K1, V1), Tag.Create(K2, V2) }.GetEnumerator(); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TagKeyTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagKeyTest.cs new file mode 100644 index 000000000..bbc9b5c2c --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagKeyTest.cs @@ -0,0 +1,86 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System; + using Xunit; + + public class TagKeyTest + { + [Fact] + public void TestMaxLength() + { + Assert.Equal(255, TagKey.MaxLength); + } + + [Fact] + public void TestGetName() + { + Assert.Equal("foo", TagKey.Create("foo").Name); + } + + [Fact] + public void Create_AllowTagKeyNameWithMaxLength() + { + char[] chars = new char[TagKey.MaxLength]; + for (int i = 0; i < chars.Length; i++) + { + chars[i] = 'k'; + } + + String key = new String(chars); + Assert.Equal(key, TagKey.Create(key).Name); + } + + [Fact] + public void Create_DisallowTagKeyNameOverMaxLength() + { + char[] chars = new char[TagKey.MaxLength + 1]; + for (int i = 0; i < chars.Length; i++) + { + chars[i] = 'k'; + } + + String key = new String(chars); + Assert.Throws(() => TagKey.Create(key)); + } + + [Fact] + public void Create_DisallowUnprintableChars() + { + Assert.Throws(() => TagKey.Create("\u02ab\u03cd")); + } + + [Fact] + public void CreateString_DisallowEmpty() + { + Assert.Throws(() => TagKey.Create("")); + } + + [Fact] + public void TestTagKeyEquals() + { + var key1 = TagKey.Create("foo"); + var key2 = TagKey.Create("foo"); + var key3 = TagKey.Create("bar"); + Assert.Equal(key1, key2); + Assert.NotEqual(key3, key1); + Assert.NotEqual(key3, key2); + + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TagTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagTest.cs new file mode 100644 index 000000000..b8eecc502 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagTest.cs @@ -0,0 +1,45 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using Xunit; + + public class TagTest + { + [Fact] + public void TestGetKey() + { + Assert.Equal(TagKey.Create("k"), Tag.Create(TagKey.Create("k"), TagValue.Create("v")).Key); + } + + [Fact] + public void TestTagEquals() + { + ITag tag1 = Tag.Create(TagKey.Create("Key"), TagValue.Create("foo")); + ITag tag2 = Tag.Create(TagKey.Create("Key"), TagValue.Create("foo")); + ITag tag3 = Tag.Create(TagKey.Create("Key"), TagValue.Create("bar")); + ITag tag4 = Tag.Create(TagKey.Create("Key2"), TagValue.Create("foo")); + Assert.Equal(tag1, tag2); + Assert.NotEqual(tag1, tag3); + Assert.NotEqual(tag1, tag4); + Assert.NotEqual(tag2, tag3); + Assert.NotEqual(tag2, tag4); + Assert.NotEqual(tag3, tag4); + + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TagValueTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagValueTest.cs new file mode 100644 index 000000000..066da53d9 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagValueTest.cs @@ -0,0 +1,82 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System; + using Xunit; + + public class TagValueTest + { + [Fact] + public void TestMaxLength() + { + Assert.Equal(255, TagValue.MaxLength); + } + + [Fact] + public void TestAsString() + { + Assert.Equal("foo", TagValue.Create("foo").AsString); + } + + [Fact] + public void Create_AllowTagValueWithMaxLength() + { + char[] chars = new char[TagValue.MaxLength]; + for (int i = 0; i < chars.Length; i++) + { + chars[i] = 'v'; + } + + String value = new String(chars); + Assert.Equal(value, TagValue.Create(value).AsString); + } + + [Fact] + public void Create_DisallowTagValueOverMaxLength() + { + char[] chars = new char[TagValue.MaxLength + 1]; + for (int i = 0; i < chars.Length; i++) + { + chars[i] = 'v'; + } + + String value = new String(chars); + Assert.Throws(() => TagValue.Create(value)); + } + + [Fact] + public void DisallowTagValueWithUnprintableChars() + { + String value = "\u02ab\u03cd"; + Assert.Throws(() => TagValue.Create(value)); + } + + [Fact] + public void TestTagValueEquals() + { + var v1 = TagValue.Create("foo"); + var v2 = TagValue.Create("foo"); + var v3 = TagValue.Create("bar"); + Assert.Equal(v1, v2); + Assert.NotEqual(v1, v3); + Assert.NotEqual(v2, v3); + Assert.Equal(v3, v3); + + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TaggerTest.cs b/test/OpenCensus.Tests/Impl/Tags/TaggerTest.cs new file mode 100644 index 000000000..46d5b7795 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TaggerTest.cs @@ -0,0 +1,350 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Tags.Unsafe; + using Xunit; + + public class TaggerTest + { + private readonly TagsComponent tagsComponent = new TagsComponent(); + private readonly ITagger tagger; + + private static readonly ITagKey K1 = TagKey.Create("k1"); + private static readonly ITagKey K2 = TagKey.Create("k2"); + private static readonly ITagKey K3 = TagKey.Create("k3"); + + private static readonly ITagValue V1 = TagValue.Create("v1"); + private static readonly ITagValue V2 = TagValue.Create("v2"); + private static readonly ITagValue V3 = TagValue.Create("v3"); + + private static readonly ITag TAG1 = Tag.Create(K1, V1); + private static readonly ITag TAG2 = Tag.Create(K2, V2); + private static readonly ITag TAG3 = Tag.Create(K3, V3); + + public TaggerTest() + { + tagger = tagsComponent.Tagger; + } + + [Fact] + public void Empty() + { + Assert.Empty(TagsTestUtil.TagContextToList(tagger.Empty)); + Assert.IsType(tagger.Empty); + } + + // [Fact] + // public void Empty_TaggingDisabled() + // { + // tagsComponent.State =TaggingState.DISABLED); + // Assert.Empty(TagsTestUtil.TagContextToList(tagger.Empty)).isEmpty(); + // Assert.IsType(tagger.Empty); + // } + + [Fact] + public void EmptyBuilder() + { + ITagContextBuilder builder = tagger.EmptyBuilder; + Assert.IsType(builder); + Assert.Empty(TagsTestUtil.TagContextToList(builder.Build())); + } + + // [Fact] + // public void EmptyBuilder_TaggingDisabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(tagger.EmptyBuilder).isSameAs(NoopTagContextBuilder.Instance); + // } + + // [Fact] + // public void EmptyBuilder_TaggingReenabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(tagger.EmptyBuilder).isSameAs(NoopTagContextBuilder.Instance); + // tagsComponent.setState(TaggingState.ENABLED); + // TagContextBuilder builder = tagger.EmptyBuilder; + // Assert.Equal(builder).isInstanceOf(TagContextBuilder); + // Assert.Equal(TagsTestUtil.TagContextToList(builder.put(K1, V1).Build())).containsExactly(Tag.Create(K1, V1)); + // } + + [Fact] + public void CurrentBuilder() + { + ITagContext tags = new SimpleTagContext(TAG1, TAG2, TAG3); + ITagContextBuilder result = GetResultOfCurrentBuilder(tags); + Assert.IsType(result); + Assert.Equal(new List() { TAG1, TAG2, TAG3 }, TagsTestUtil.TagContextToList(result.Build())); + } + + [Fact] + public void CurrentBuilder_DefaultIsEmpty() + { + ITagContextBuilder currentBuilder = tagger.CurrentBuilder; + Assert.IsType(currentBuilder); + Assert.Empty(TagsTestUtil.TagContextToList(currentBuilder.Build())); + } + + [Fact] + public void CurrentBuilder_RemoveDuplicateTags() + { + ITag tag1 = Tag.Create(K1, V1); + ITag tag2 = Tag.Create(K1, V2); + ITagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + ITagContextBuilder result = GetResultOfCurrentBuilder(tagContextWithDuplicateTags); + Assert.Equal(new List() { tag2 }, TagsTestUtil.TagContextToList(result.Build())); + } + + [Fact] + public void CurrentBuilder_SkipNullTag() + { + ITagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + ITagContextBuilder result = GetResultOfCurrentBuilder(tagContextWithNullTag); + Assert.Equal(new List() { TAG1, TAG2 }, TagsTestUtil.TagContextToList(result.Build())); + } + + // [Fact] + // public void CurrentBuilder_TaggingDisabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(getResultOfCurrentBuilder(new SimpleTagContext(TAG1))) + // .isSameAs(NoopTagContextBuilder.Instance); + // } + + // [Fact] + // public void currentBuilder_TaggingReenabled() + // { + // TagContext tags = new SimpleTagContext(TAG1); + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(getResultOfCurrentBuilder(tags)).isSameAs(NoopTagContextBuilder.Instance); + // tagsComponent.setState(TaggingState.ENABLED); + // TagContextBuilder builder = getResultOfCurrentBuilder(tags); + // Assert.Equal(builder).isInstanceOf(TagContextBuilder); + // Assert.Equal(TagsTestUtil.TagContextToList(builder.Build())).containsExactly(TAG1); + // } + + private ITagContextBuilder GetResultOfCurrentBuilder(ITagContext tagsToSet) + { + ITagContext orig = AsyncLocalContext.CurrentTagContext; // Context.current().withValue(ContextUtils.TAG_CONTEXT_KEY, tagsToSet).attach(); + AsyncLocalContext.CurrentTagContext = tagsToSet; + try + { + return tagger.CurrentBuilder; + } + finally + { + AsyncLocalContext.CurrentTagContext = orig; + } + } + + [Fact] + public void ToBuilder_ConvertUnknownTagContextToTagContext() + { + ITagContext unknownTagContext = new SimpleTagContext(TAG1, TAG2, TAG3); + ITagContext newTagContext = tagger.ToBuilder(unknownTagContext).Build(); + Assert.Equal(new List() { TAG1, TAG2, TAG3 }, TagsTestUtil.TagContextToList(newTagContext)); + Assert.IsType(newTagContext); + } + + [Fact] + public void ToBuilder_RemoveDuplicatesFromUnknownTagContext() + { + ITag tag1 = Tag.Create(K1, V1); + ITag tag2 = Tag.Create(K1, V2); + ITagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + ITagContext newTagContext = tagger.ToBuilder(tagContextWithDuplicateTags).Build(); + Assert.Equal(new List() { tag2 }, TagsTestUtil.TagContextToList(newTagContext)); + } + + [Fact] + public void ToBuilder_SkipNullTag() + { + ITagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + ITagContext newTagContext = tagger.ToBuilder(tagContextWithNullTag).Build(); + Assert.Equal(new List() { TAG1, TAG2 }, TagsTestUtil.TagContextToList(newTagContext)); + } + + // [Fact] + // public void ToBuilder_TaggingDisabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(tagger.ToBuilder(new SimpleTagContext(TAG1))) + // .isSameAs(NoopTagContextBuilder.Instance); + // } + + // [Fact] + // public void ToBuilder_TaggingReenabled() + // { + // TagContext tags = new SimpleTagContext(TAG1); + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(tagger.ToBuilder(tags)).isSameAs(NoopTagContextBuilder.Instance); + // tagsComponent.setState(TaggingState.ENABLED); + // TagContextBuilder builder = tagger.ToBuilder(tags); + // Assert.Equal(builder).isInstanceOf(TagContextBuilder); + // Assert.Equal(TagsTestUtil.TagContextToList(builder.Build())).containsExactly(TAG1); + // } + + [Fact] + public void GetCurrentTagContext_DefaultIsEmptyTagContext() + { + ITagContext currentTagContext = tagger.CurrentTagContext; + Assert.Empty(TagsTestUtil.TagContextToList(currentTagContext)); + Assert.IsType(currentTagContext); + } + + [Fact] + public void GetCurrentTagContext_ConvertUnknownTagContextToTagContext() + { + ITagContext unknownTagContext = new SimpleTagContext(TAG1, TAG2, TAG3); + ITagContext result = GetResultOfGetCurrentTagContext(unknownTagContext); + Assert.IsType(result); + Assert.Equal(new List() { TAG1, TAG2, TAG3 }, TagsTestUtil.TagContextToList(result)); + } + + [Fact] + public void GetCurrentTagContext_RemoveDuplicatesFromUnknownTagContext() + { + ITag tag1 = Tag.Create(K1, V1); + ITag tag2 = Tag.Create(K1, V2); + ITagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + ITagContext result = GetResultOfGetCurrentTagContext(tagContextWithDuplicateTags); + Assert.Equal(new List() { tag2 }, TagsTestUtil.TagContextToList(result)); + } + + [Fact] + public void GetCurrentTagContext_SkipNullTag() + { + ITagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + ITagContext result = GetResultOfGetCurrentTagContext(tagContextWithNullTag); + Assert.Equal(new List() { TAG1, TAG2 }, TagsTestUtil.TagContextToList(result)); + } + + // [Fact] + // public void GetCurrentTagContext_TaggingDisabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(TagsTestUtil.TagContextToList(getResultOfGetCurrentTagContext(new SimpleTagContext(TAG1)))) + // .isEmpty(); + // } + + // [Fact] + // public void getCurrentTagContext_TaggingReenabled() + // { + // TagContext tags = new SimpleTagContext(TAG1); + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(TagsTestUtil.TagContextToList(getResultOfGetCurrentTagContext(tags))).isEmpty(); + // tagsComponent.setState(TaggingState.ENABLED); + // Assert.Equal(TagsTestUtil.TagContextToList(getResultOfGetCurrentTagContext(tags))).containsExactly(TAG1); + // } + + private ITagContext GetResultOfGetCurrentTagContext(ITagContext tagsToSet) + { + ITagContext orig = AsyncLocalContext.CurrentTagContext; + AsyncLocalContext.CurrentTagContext = tagsToSet; + // Context orig = Context.current().withValue(ContextUtils.TAG_CONTEXT_KEY, tagsToSet).attach(); + try + { + return tagger.CurrentTagContext; + } + finally + { + AsyncLocalContext.CurrentTagContext = orig; + } + } + + [Fact] + public void WithTagContext_ConvertUnknownTagContextToTagContext() + { + ITagContext unknownTagContext = new SimpleTagContext(TAG1, TAG2, TAG3); + ITagContext result = GetResultOfWithTagContext(unknownTagContext); + Assert.IsType(result); + Assert.Equal(new List() { TAG1, TAG2, TAG3 }, TagsTestUtil.TagContextToList(result)); + } + + [Fact] + public void WithTagContext_RemoveDuplicatesFromUnknownTagContext() + { + ITag tag1 = Tag.Create(K1, V1); + ITag tag2 = Tag.Create(K1, V2); + ITagContext tagContextWithDuplicateTags = new SimpleTagContext(tag1, tag2); + ITagContext result = GetResultOfWithTagContext(tagContextWithDuplicateTags); + Assert.Equal(new List() { tag2 }, TagsTestUtil.TagContextToList(result)); + } + + [Fact] + public void WithTagContext_SkipNullTag() + { + ITagContext tagContextWithNullTag = new SimpleTagContext(TAG1, null, TAG2); + ITagContext result = GetResultOfWithTagContext(tagContextWithNullTag); + Assert.Equal(new List() { TAG1, TAG2 }, TagsTestUtil.TagContextToList(result)); + } + + // [Fact] + // public void WithTagContext_ReturnsNoopScopeWhenTaggingIsDisabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(tagger.withTagContext(new SimpleTagContext(TAG1))).isSameAs(NoopScope.getInstance()); + // } + + // [Fact] + // public void withTagContext_TaggingDisabled() + // { + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(TagsTestUtil.TagContextToList(getResultOfWithTagContext(new SimpleTagContext(TAG1)))).isEmpty(); + // } + + // [Fact] + // public void WithTagContext_TaggingReenabled() + // { + // ITagContext tags = new SimpleTagContext(TAG1); + // tagsComponent.setState(TaggingState.DISABLED); + // Assert.Equal(TagsTestUtil.TagContextToList(getResultOfWithTagContext(tags))).isEmpty(); + // tagsComponent.setState(TaggingState.ENABLED); + // Assert.Equal(TagsTestUtil.TagContextToList(getResultOfWithTagContext(tags))).containsExactly(TAG1); + // } + + private ITagContext GetResultOfWithTagContext(ITagContext tagsToSet) + { + IScope scope = tagger.WithTagContext(tagsToSet); + try + { + return AsyncLocalContext.CurrentTagContext; + } + finally + { + scope.Dispose(); + } + } + + class SimpleTagContext : TagContextBase + { + private readonly List tags; + + public SimpleTagContext(params ITag[] tags) + { + this.tags = new List(tags); + } + + public override IEnumerator GetEnumerator() + { + return tags.GetEnumerator(); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TagsComponentBaseTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagsComponentBaseTest.cs new file mode 100644 index 000000000..35f9d5649 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagsComponentBaseTest.cs @@ -0,0 +1,31 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using Xunit; + + public class TagsComponentBaseTest + { + private readonly TagsComponent tagsComponent = new TagsComponent(); + + [Fact] + public void DefaultState() + { + Assert.Equal(TaggingState.ENABLED, tagsComponent.State); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TagsDefaultTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagsDefaultTest.cs new file mode 100644 index 000000000..9762f600c --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagsDefaultTest.cs @@ -0,0 +1,49 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using Xunit; + + public class TagsDefaultTest + { + [Fact(Skip = "Fix statics usage")] + public void TestState() + { + // Test that setState ignores its input. + // Tags.setState(TaggingState.ENABLED); + Assert.Equal(TaggingState.DISABLED, Tags.State); + + // Test that setState cannot be called after getState. + // thrown.expect(IllegalStateException); + // thrown.expectMessage("State was already read, cannot set state."); + // Tags.setState(TaggingState.ENABLED); + } + + [Fact(Skip = "Fix statics usage")] + public void DefaultTagger() + { + Assert.Equal(NoopTags.NoopTagger, Tags.Tagger); + } + + [Fact(Skip = "Fix statics usage")] + public void DefaultTagContextSerializer() + { + Assert.Equal(NoopTags.NoopTagPropagationComponent, Tags.TagPropagationComponent); + } + + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TagsTest.cs b/test/OpenCensus.Tests/Impl/Tags/TagsTest.cs new file mode 100644 index 000000000..7771c0368 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagsTest.cs @@ -0,0 +1,41 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using OpenCensus.Tags.Propagation; + using Xunit; + + public class TagsTest + { + public TagsTest() + { + Tags.Initialize(true); + } + + [Fact] + public void GetTagger() + { + Assert.Equal(typeof(Tagger), Tags.Tagger.GetType()); + } + + [Fact] + public void GetTagContextSerializer() + { + Assert.Equal(typeof(TagPropagationComponent), Tags.TagPropagationComponent.GetType()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Tags/TagsTestUtil.cs b/test/OpenCensus.Tests/Impl/Tags/TagsTestUtil.cs new file mode 100644 index 000000000..eff2facfc --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Tags/TagsTestUtil.cs @@ -0,0 +1,29 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Tags.Test +{ + using System.Collections.Generic; + using System.Linq; + + internal static class TagsTestUtil + { + public static ICollection TagContextToList(ITagContext tags) + { + return tags.ToList(); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Testing/Export/TestHandler.cs b/test/OpenCensus.Tests/Impl/Testing/Export/TestHandler.cs new file mode 100644 index 000000000..9035763dd --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Testing/Export/TestHandler.cs @@ -0,0 +1,66 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Testing.Export +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using OpenCensus.Trace.Export; + + public class TestHandler : IHandler + { + private readonly object monitor = new object(); + private readonly List spanDataList = new List(); + + public async Task ExportAsync(IEnumerable data) + { + lock (monitor) + { + this.spanDataList.AddRange(data); + Monitor.PulseAll(monitor); + } + + } + + public IEnumerable WaitForExport(int numberOfSpans) + { + var result = new List(); + lock (monitor) { + while (spanDataList.Count < numberOfSpans) + { + try + { + if (!Monitor.Wait(monitor, 5000)) + { + return result; + } + } + catch (Exception) + { + // Preserve the interruption status as per guidance. + // Thread.currentThread().interrupt(); + return result; + } + } + result = new List(spanDataList); + spanDataList.Clear(); + } + return result; + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/AnnotationTest.cs b/test/OpenCensus.Tests/Impl/Trace/AnnotationTest.cs new file mode 100644 index 000000000..012811f8a --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/AnnotationTest.cs @@ -0,0 +1,105 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Utils; + using Xunit; + + public class AnnotationTest + { + [Fact] + public void FromDescription_NullDescription() + { + Assert.Throws(() => Annotation.FromDescription(null)); + } + + [Fact] + public void FromDescription() + { + IAnnotation annotation = Annotation.FromDescription("MyAnnotationText"); + Assert.Equal("MyAnnotationText", annotation.Description); + Assert.Equal(0, annotation.Attributes.Count); + } + + [Fact] + public void FromDescriptionAndAttributes_NullDescription() + { + Assert.Throws(() => Annotation.FromDescriptionAndAttributes(null, new Dictionary())); + } + + [Fact] + public void FromDescriptionAndAttributes_NullAttributes() + { + Assert.Throws(() => Annotation.FromDescriptionAndAttributes("", null)); + } + + [Fact] + public void FromDescriptionAndAttributes() + { + Dictionary attributes = new Dictionary(); + attributes.Add( + "MyStringAttributeKey", AttributeValue.Create("MyStringAttributeValue")); + IAnnotation annotation = Annotation.FromDescriptionAndAttributes("MyAnnotationText", attributes); + Assert.Equal("MyAnnotationText", annotation.Description); + Assert.Equal(attributes, annotation.Attributes); + } + + [Fact] + public void FromDescriptionAndAttributes_EmptyAttributes() + { + IAnnotation annotation = + Annotation.FromDescriptionAndAttributes( + "MyAnnotationText", new Dictionary()); + Assert.Equal("MyAnnotationText", annotation.Description); + Assert.Equal(0, annotation.Attributes.Count); + } + + [Fact] + public void Annotation_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // Map attributes = new HashMap(); + // attributes.put( + // "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue")); + // tester + // .addEqualityGroup( + // Annotation.fromDescription("MyAnnotationText"), + // Annotation.fromDescriptionAndAttributes( + // "MyAnnotationText", Collections.< String, AttributeValue > emptyMap())) + // .addEqualityGroup( + // Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes), + // Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes)) + // .addEqualityGroup(Annotation.fromDescription("MyAnnotationText2")); + // tester.testEquals(); + } + + [Fact] + public void Annotation_ToString() + { + IAnnotation annotation = Annotation.FromDescription("MyAnnotationText"); + Assert.Contains("MyAnnotationText", annotation.ToString()); + Dictionary attributes = new Dictionary(); + attributes.Add( + "MyStringAttributeKey", AttributeValue.Create("MyStringAttributeValue")); + annotation = Annotation.FromDescriptionAndAttributes("MyAnnotationText2", attributes); + Assert.Contains("MyAnnotationText2", annotation.ToString()); + Assert.Contains(Collections.ToString(attributes), annotation.ToString()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/AttributeValueTest.cs b/test/OpenCensus.Tests/Impl/Trace/AttributeValueTest.cs new file mode 100644 index 000000000..b31223f7a --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/AttributeValueTest.cs @@ -0,0 +1,81 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class AttributeValueTest + { + + [Fact] + public void StringAttributeValue() + { + IAttributeValue attribute = AttributeValue.Create("MyStringAttributeValue"); + attribute.Apply((stringValue) => + { + Assert.Equal("MyStringAttributeValue", stringValue); + return null; + }); + } + + [Fact] + public void BooleanAttributeValue() + { + IAttributeValue attribute = AttributeValue.Create(true); + attribute.Apply((boolValue) => + { + Assert.True(boolValue); + return null; + }); + } + + [Fact] + public void LongAttributeValue() + { + IAttributeValue attribute = AttributeValue.Create(123456L); + attribute.Apply((longValue) => + { + Assert.Equal(123456L, longValue); + return null; + }); + } + + [Fact] + public void DoubleAttributeValue() + { + var attribute = AttributeValue.Create(0.005); + attribute.Apply((val) => + { + Assert.Equal(0.005, val); + return null; + }); + } + + [Fact] + public void AttributeValue_ToString() + { + IAttributeValue attribute = AttributeValue.Create("MyStringAttributeValue"); + Assert.Contains("MyStringAttributeValue", attribute.ToString()); + IAttributeValue attribute2 = AttributeValue.Create(true); + Assert.Contains("True", attribute2.ToString()); + IAttributeValue attribute3 = AttributeValue.Create(123456L); + Assert.Contains("123456", attribute3.ToString()); + } + } +} + + diff --git a/test/OpenCensus.Tests/Impl/Trace/BlankSpanTest.cs b/test/OpenCensus.Tests/Impl/Trace/BlankSpanTest.cs new file mode 100644 index 000000000..f73eb5e96 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/BlankSpanTest.cs @@ -0,0 +1,62 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System.Collections.Generic; + using OpenCensus.Trace.Internal; + using Xunit; + + public class BlankSpanTest + { + [Fact] + public void HasInvalidContextAndDefaultSpanOptions() + { + Assert.Equal(SpanContext.Invalid, BlankSpan.Instance.Context); + Assert.True(BlankSpan.Instance.Options.HasFlag(SpanOptions.None)); + } + + [Fact] + public void DoNotCrash() + { + IDictionary attributes = new Dictionary(); + attributes.Add( + "MyStringAttributeKey", AttributeValue.Create("MyStringAttributeValue")); + IDictionary multipleAttributes = new Dictionary(); + multipleAttributes.Add( + "MyStringAttributeKey", AttributeValue.Create("MyStringAttributeValue")); + multipleAttributes.Add("MyBooleanAttributeKey", AttributeValue.Create(true)); + multipleAttributes.Add("MyLongAttributeKey", AttributeValue.Create(123)); + multipleAttributes.Add("MyDoubleAttributeKey", AttributeValue.Create(0.005)); + // Tests only that all the methods are not crashing/throwing errors. + BlankSpan.Instance.PutAttribute( + "MyStringAttributeKey2", AttributeValue.Create("MyStringAttributeValue2")); + BlankSpan.Instance.PutAttributes(attributes); + BlankSpan.Instance.PutAttributes(multipleAttributes); + BlankSpan.Instance.AddAnnotation("MyAnnotation"); + BlankSpan.Instance.AddAnnotation("MyAnnotation", attributes); + BlankSpan.Instance.AddAnnotation("MyAnnotation", multipleAttributes); + BlankSpan.Instance.AddAnnotation(Annotation.FromDescription("MyAnnotation")); + // BlankSpan.Instance.addNetworkEvent(NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).build()); + BlankSpan.Instance.AddMessageEvent(MessageEvent.Builder(MessageEventType.Sent, 1L).Build()); + BlankSpan.Instance.AddLink( + Link.FromSpanContext(SpanContext.Invalid, LinkType.ChildLinkedSpan)); + BlankSpan.Instance.Status = Status.Ok; + BlankSpan.Instance.End(EndSpanOptions.Default); + BlankSpan.Instance.End(); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigBaseTest.cs new file mode 100644 index 000000000..3a7724e28 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigBaseTest.cs @@ -0,0 +1,48 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config.Test +{ + using OpenCensus.Trace.Sampler; + using Xunit; + + public class TraceConfigBaseTest + { + ITraceConfig traceConfig = TraceConfigBase.NoopTraceConfig; + + [Fact] + public void ActiveTraceParams_NoOpImplementation() + { + Assert.Equal(TraceParams.Default, traceConfig.ActiveTraceParams); + } + + [Fact] + public void UpdateActiveTraceParams_NoOpImplementation() + { + TraceParams traceParams = + TraceParams.Default + .ToBuilder() + .SetSampler(Samplers.AlwaysSample) + .SetMaxNumberOfAttributes(8) + .SetMaxNumberOfAnnotations(9) + .SetMaxNumberOfMessageEvents(10) + .SetMaxNumberOfLinks(11) + .Build(); + traceConfig.UpdateActiveTraceParams(traceParams); + Assert.Equal(TraceParams.Default, traceConfig.ActiveTraceParams); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigTest.cs b/test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigTest.cs new file mode 100644 index 000000000..7d563e6b1 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Config/TraceConfigTest.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config.Test +{ + using OpenCensus.Trace.Sampler; + using Xunit; + + public class TraceConfigTest + { + private readonly TraceConfig traceConfig = new TraceConfig(); + + [Fact] + public void DefaultActiveTraceParams() + { + Assert.Equal(TraceParams.Default, traceConfig.ActiveTraceParams); + } + + [Fact] + public void UpdateTraceParams() + { + TraceParams traceParams = + TraceParams.Default + .ToBuilder() + .SetSampler(Samplers.AlwaysSample) + .SetMaxNumberOfAttributes(8) + .SetMaxNumberOfAnnotations(9) + // .SetMaxNumberOfNetworkEvents(10) + .SetMaxNumberOfLinks(11) + .Build(); + traceConfig.UpdateActiveTraceParams(traceParams); + Assert.Equal(traceParams, traceConfig.ActiveTraceParams); + traceConfig.UpdateActiveTraceParams(TraceParams.Default); + Assert.Equal(TraceParams.Default, traceConfig.ActiveTraceParams); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Config/TraceParamsTest.cs b/test/OpenCensus.Tests/Impl/Trace/Config/TraceParamsTest.cs new file mode 100644 index 000000000..6b9531200 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Config/TraceParamsTest.cs @@ -0,0 +1,88 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Config.Test +{ + using System; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Sampler; + using Xunit; + + public class TraceParamsTest + { + [Fact] + public void DefaultTraceParams() + { + Assert.Equal(Samplers.GetProbabilitySampler(1e-4), TraceParams.Default.Sampler); + Assert.Equal(32, TraceParams.Default.MaxNumberOfAttributes); + Assert.Equal(32, TraceParams.Default.MaxNumberOfAnnotations); + Assert.Equal(128, TraceParams.Default.MaxNumberOfMessageEvents); + Assert.Equal(128, TraceParams.Default.MaxNumberOfLinks); + } + + [Fact] + public void UpdateTraceParams_NullSampler() + { + Assert.Throws(() => TraceParams.Default.ToBuilder().SetSampler(null)); + } + + [Fact] + public void UpdateTraceParams_NonPositiveMaxNumberOfAttributes() + { + Assert.Throws(() => TraceParams.Default.ToBuilder().SetMaxNumberOfAttributes(0).Build()); + } + + [Fact] + public void UpdateTraceParams_NonPositiveMaxNumberOfAnnotations() + { + Assert.Throws(() => TraceParams.Default.ToBuilder().SetMaxNumberOfAnnotations(0).Build()); + } + + + [Fact] + public void updateTraceParams_NonPositiveMaxNumberOfMessageEvents() + { + Assert.Throws(() => TraceParams.Default.ToBuilder().SetMaxNumberOfMessageEvents(0).Build()); + } + + [Fact] + public void updateTraceParams_NonPositiveMaxNumberOfLinks() + { + Assert.Throws(() => TraceParams.Default.ToBuilder().SetMaxNumberOfLinks(0).Build()); + } + + [Fact] + public void UpdateTraceParams_All() + { + TraceParams traceParams = + TraceParams.Default + .ToBuilder() + .SetSampler(Samplers.AlwaysSample) + .SetMaxNumberOfAttributes(8) + .SetMaxNumberOfAnnotations(9) + .SetMaxNumberOfMessageEvents(10) + .SetMaxNumberOfLinks(11) + .Build(); + + Assert.Equal(Samplers.AlwaysSample, traceParams.Sampler); + Assert.Equal(8, traceParams.MaxNumberOfAttributes); + Assert.Equal(9, traceParams.MaxNumberOfAnnotations); + // test maxNumberOfNetworkEvent can be set via maxNumberOfMessageEvent + Assert.Equal(10, traceParams.MaxNumberOfMessageEvents); + Assert.Equal(11, traceParams.MaxNumberOfLinks); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/CurrentSpanUtilsTest.cs b/test/OpenCensus.Tests/Impl/Trace/CurrentSpanUtilsTest.cs new file mode 100644 index 000000000..68886ebee --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/CurrentSpanUtilsTest.cs @@ -0,0 +1,85 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Moq; + using OpenCensus.Common; + using OpenCensus.Trace.Internal; + using Xunit; + + public class CurrentSpanUtilsTest + { + private ISpan span; + private RandomGenerator random; + private ISpanContext spanContext; + private SpanOptions spanOptions; + + public CurrentSpanUtilsTest() + { + random = new RandomGenerator(1234); + spanContext = + SpanContext.Create( + TraceId.GenerateRandomId(random), + SpanId.GenerateRandomId(random), + TraceOptions.Builder().SetIsSampled(true).Build(), + Tracestate.Empty); + + spanOptions = SpanOptions.RecordEvents; + var mockSpan = new Mock(spanContext, spanOptions) { CallBase = true }; + span = mockSpan.Object; + } + + [Fact] + public void CurrentSpan_WhenNoContext() + { + Assert.Null(CurrentSpanUtils.CurrentSpan); + } + + [Fact] + public void WithSpan_CloseDetaches() + { + Assert.Null(CurrentSpanUtils.CurrentSpan); + IScope ws = CurrentSpanUtils.WithSpan(span, false); + try + { + Assert.Same(span, CurrentSpanUtils.CurrentSpan); + } + finally + { + ws.Dispose(); + } + Assert.Null(CurrentSpanUtils.CurrentSpan); + } + + [Fact] + public void WithSpan_CloseDetachesAndEndsSpan() + { + Assert.Null(CurrentSpanUtils.CurrentSpan); + IScope ss = CurrentSpanUtils.WithSpan(span, true); + try + { + Assert.Same(span, CurrentSpanUtils.CurrentSpan); + } + finally + { + ss.Dispose(); + } + Assert.Null(CurrentSpanUtils.CurrentSpan); + Mock.Get(span).Verify((s) => s.End(EndSpanOptions.Default)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/EndSpanOptionsTest.cs b/test/OpenCensus.Tests/Impl/Trace/EndSpanOptionsTest.cs new file mode 100644 index 000000000..23f003219 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/EndSpanOptionsTest.cs @@ -0,0 +1,80 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class EndSpanOptionsTest + { + [Fact] + public void EndSpanOptions_DefaultOptions() + { + Assert.Null(EndSpanOptions.Default.Status); + Assert.False(EndSpanOptions.Default.SampleToLocalSpanStore); + } + + [Fact] + public void SetStatus_Ok() + { + EndSpanOptions endSpanOptions = EndSpanOptions.Builder().SetStatus(Status.Ok).Build(); + Assert.Equal(Status.Ok, endSpanOptions.Status); + } + + [Fact] + public void SetStatus_Error() + { + EndSpanOptions endSpanOptions = + EndSpanOptions.Builder() + .SetStatus(Status.Cancelled.WithDescription("ThisIsAnError")) + .Build(); + Assert.Equal(Status.Cancelled.WithDescription("ThisIsAnError"), endSpanOptions.Status); + } + + [Fact] + public void SetSampleToLocalSpanStore() + { + EndSpanOptions endSpanOptions = + EndSpanOptions.Builder().SetSampleToLocalSpanStore(true).Build(); + Assert.True(endSpanOptions.SampleToLocalSpanStore); + } + + [Fact] + public void EndSpanOptions_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // tester.addEqualityGroup( + // EndSpanOptions.builder() + // .setStatus(Status.CANCELLED.withDescription("ThisIsAnError")) + // .build(), + // EndSpanOptions.builder() + // .setStatus(Status.CANCELLED.withDescription("ThisIsAnError")) + // .build()); + // tester.addEqualityGroup(EndSpanOptions.builder().build(), EndSpanOptions.DEFAULT); + // tester.testEquals(); + } + + [Fact] + public void EndSpanOptions_ToString() + { + EndSpanOptions endSpanOptions = + EndSpanOptions.Builder() + .SetStatus(Status.Cancelled.WithDescription("ThisIsAnError")) + .Build(); + Assert.Contains("ThisIsAnError", endSpanOptions.ToString()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentBaseTest.cs new file mode 100644 index 000000000..baabab502 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentBaseTest.cs @@ -0,0 +1,43 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using Xunit; + + public class ExportComponentBaseTest + { + private readonly IExportComponent exportComponent = ExportComponentBase.NewNoopExportComponent; + + [Fact] + public void ImplementationOfSpanExporter() + { + Assert.Equal(SpanExporter.NoopSpanExporter, exportComponent.SpanExporter); + } + + [Fact] + public void ImplementationOfActiveSpans() + { + Assert.Equal(RunningSpanStoreBase.NoopRunningSpanStore, exportComponent.RunningSpanStore); + } + + [Fact] + public void ImplementationOfSampledSpanStore() + { + Assert.Equal(SampledSpanStoreBase.NewNoopSampledSpanStore.GetType(), exportComponent.SampledSpanStore.GetType()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentTest.cs new file mode 100644 index 000000000..2e66b2992 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/ExportComponentTest.cs @@ -0,0 +1,47 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using OpenCensus.Internal; + using Xunit; + + public class ExportComponentTest + { + private readonly IExportComponent exportComponentWithInProcess = ExportComponent.CreateWithInProcessStores(new SimpleEventQueue()); + private readonly IExportComponent exportComponentWithoutInProcess = ExportComponent.CreateWithoutInProcessStores(new SimpleEventQueue()); + + [Fact] + public void ImplementationOfSpanExporter() + { + Assert.IsType(exportComponentWithInProcess.SpanExporter); + } + + [Fact] + public void ImplementationOfActiveSpans() + { + Assert.IsType(exportComponentWithInProcess.RunningSpanStore); + Assert.IsType(exportComponentWithoutInProcess.RunningSpanStore); + } + + [Fact] + public void ImplementationOfSampledSpanStore() + { + Assert.IsType(exportComponentWithInProcess.SampledSpanStore); + Assert.IsType(exportComponentWithoutInProcess.SampledSpanStore); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/InProcessRunningSpanStoreTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/InProcessRunningSpanStoreTest.cs new file mode 100644 index 000000000..fb204f379 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/InProcessRunningSpanStoreTest.cs @@ -0,0 +1,146 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using Xunit; + + public class InProcessRunningSpanStoreTest + { + + private static readonly string SPAN_NAME_1 = "MySpanName/1"; + private static readonly string SPAN_NAME_2 = "MySpanName/2"; + private readonly RandomGenerator random = new RandomGenerator(1234); + private readonly ISpanExporter sampledSpansServiceExporter = SpanExporter.Create(4, Duration.Create(1, 0)); + private readonly InProcessRunningSpanStore activeSpansExporter = new InProcessRunningSpanStore(); + private readonly StartEndHandler startEndHandler; + private SpanOptions recordSpanOptions = SpanOptions.RecordEvents; + + public InProcessRunningSpanStoreTest() + { + startEndHandler = new StartEndHandler(sampledSpansServiceExporter, activeSpansExporter, null, new SimpleEventQueue()); + } + + private ISpan CreateSpan(string spanName) + { + ISpanContext spanContext = + SpanContext.Create( + TraceId.GenerateRandomId(random), + SpanId.GenerateRandomId(random), + TraceOptions.Default, Tracestate.Empty); + return Span.StartSpan( + spanContext, + recordSpanOptions, + spanName, + SpanId.GenerateRandomId(random), + false, + TraceParams.Default, + startEndHandler, + null); + } + + [Fact] + public void GetSummary_SpansWithDifferentNames() + { + ISpan span1 = CreateSpan(SPAN_NAME_1); + ISpan span2 = CreateSpan(SPAN_NAME_2); + Assert.Equal(2, activeSpansExporter.Summary.PerSpanNameSummary.Count); + Assert.Equal(1, + activeSpansExporter + .Summary + .PerSpanNameSummary[SPAN_NAME_1] + .NumRunningSpans); + Assert.Equal(1, + activeSpansExporter + .Summary + .PerSpanNameSummary[SPAN_NAME_2] + .NumRunningSpans); + span1.End(); + Assert.Equal(1, activeSpansExporter.Summary.PerSpanNameSummary.Count); + Assert.False(activeSpansExporter.Summary.PerSpanNameSummary.ContainsKey(SPAN_NAME_1)); + Assert.Equal(1, + activeSpansExporter + .Summary + .PerSpanNameSummary[SPAN_NAME_2] + .NumRunningSpans); + span2.End(); + Assert.Equal(0, activeSpansExporter.Summary.PerSpanNameSummary.Count); + } + + [Fact] + public void GetSummary_SpansWithSameName() + { + ISpan span1 = CreateSpan(SPAN_NAME_1); + ISpan span2 = CreateSpan(SPAN_NAME_1); + ISpan span3 = CreateSpan(SPAN_NAME_1); + Assert.Equal(1, activeSpansExporter.Summary.PerSpanNameSummary.Count); + Assert.Equal(3, + activeSpansExporter + .Summary + .PerSpanNameSummary[SPAN_NAME_1] + .NumRunningSpans); + span1.End(); + Assert.Equal(1, activeSpansExporter.Summary.PerSpanNameSummary.Count); + Assert.Equal(2, + activeSpansExporter + .Summary + .PerSpanNameSummary[SPAN_NAME_1] + .NumRunningSpans); + span2.End(); + Assert.Equal(1, activeSpansExporter.Summary.PerSpanNameSummary.Count); + Assert.Equal(1, + activeSpansExporter + .Summary + .PerSpanNameSummary[SPAN_NAME_1] + .NumRunningSpans); + span3.End(); + Assert.Equal(0, activeSpansExporter.Summary.PerSpanNameSummary.Count); + } + + [Fact] + public void GetActiveSpans_SpansWithDifferentNames() + { + Span span1 = CreateSpan(SPAN_NAME_1) as Span; + Span span2 = CreateSpan(SPAN_NAME_2) as Span; + Assert.Contains(span1.ToSpanData(), activeSpansExporter.GetRunningSpans(RunningSpanStoreFilter.Create(SPAN_NAME_1, 0))); + Assert.Contains(span1.ToSpanData(), activeSpansExporter.GetRunningSpans(RunningSpanStoreFilter.Create(SPAN_NAME_1, 2))); + Assert.Contains(span2.ToSpanData(), activeSpansExporter.GetRunningSpans(RunningSpanStoreFilter.Create(SPAN_NAME_2, 0))); + span1.End(); + span2.End(); + } + + // [Fact] + // public void getActiveSpans_SpansWithSameName() + // { + // SpanImpl span1 = createSpan(SPAN_NAME_1); + // SpanImpl span2 = createSpan(SPAN_NAME_1); + // SpanImpl span3 = createSpan(SPAN_NAME_1); + // Assert.Equal(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 0))) + // .containsExactly(span1.toSpanData(), span2.toSpanData(), span3.toSpanData()); + // Assert.Equal(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 2)).size()) + // .isEqualTo(2); + // Assert.Equal(activeSpansExporter.getRunningSpans(Filter.create(SPAN_NAME_1, 2))) + // .containsAnyOf(span1.toSpanData(), span2.toSpanData(), span3.toSpanData()); + // span1.end(); + // span2.end(); + // span3.end(); + // } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/InProcessSampledSpanStoreTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/InProcessSampledSpanStoreTest.cs new file mode 100644 index 000000000..44010086d --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/InProcessSampledSpanStoreTest.cs @@ -0,0 +1,385 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using OpenCensus.Utils; + using Xunit; + + public class InProcessSampledSpanStoreTest + { + private static readonly String REGISTERED_SPAN_NAME = "MySpanName/1"; + private static readonly String NOT_REGISTERED_SPAN_NAME = "MySpanName/2"; + private readonly RandomGenerator random = new RandomGenerator(1234); + private readonly ISpanContext sampledSpanContext; + + private readonly ISpanContext notSampledSpanContext; + + private readonly ISpanId parentSpanId; + private readonly SpanOptions recordSpanOptions = SpanOptions.RecordEvents; + private TimeSpan interval = TimeSpan.FromMilliseconds(0); + private readonly DateTimeOffset startTime = DateTimeOffset.Now; + private readonly Timestamp timestamp; + private readonly Timer timestampConverter; + + private readonly InProcessSampledSpanStore sampleStore = new InProcessSampledSpanStore(new SimpleEventQueue()); + + private readonly IStartEndHandler startEndHandler; + + + public InProcessSampledSpanStoreTest() + { + timestamp = Timestamp.FromDateTimeOffset(startTime); + timestampConverter = Timer.StartNew(startTime, () => interval); + sampledSpanContext = SpanContext.Create(TraceId.GenerateRandomId(random), SpanId.GenerateRandomId(random), TraceOptions.Builder().SetIsSampled(true).Build(), Tracestate.Empty); + notSampledSpanContext = SpanContext.Create(TraceId.GenerateRandomId(random), SpanId.GenerateRandomId(random), TraceOptions.Default, Tracestate.Empty); + parentSpanId = SpanId.GenerateRandomId(random); + startEndHandler = new TestStartEndHandler(sampleStore); + sampleStore.RegisterSpanNamesForCollection(new List() { REGISTERED_SPAN_NAME }); + } + + + + [Fact] + public void AddSpansWithRegisteredNamesInAllLatencyBuckets() + { + AddSpanNameToAllLatencyBuckets(REGISTERED_SPAN_NAME); + IDictionary perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary; + Assert.Equal(1, perSpanNameSummary.Count); + IDictionary latencyBucketsSummaries = perSpanNameSummary[REGISTERED_SPAN_NAME].NumbersOfLatencySampledSpans; + Assert.Equal(LatencyBucketBoundaries.Values.Count, latencyBucketsSummaries.Count); + foreach (var it in latencyBucketsSummaries) + { + Assert.Equal(2, it.Value); + } + } + + [Fact] + public void AddSpansWithoutRegisteredNamesInAllLatencyBuckets() + { + AddSpanNameToAllLatencyBuckets(NOT_REGISTERED_SPAN_NAME); + IDictionary perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary; + Assert.Equal(1, perSpanNameSummary.Count); + Assert.False(perSpanNameSummary.ContainsKey(NOT_REGISTERED_SPAN_NAME)); + } + + [Fact] + public void RegisterUnregisterAndListSpanNames() + { + Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection); + Assert.Equal(1, sampleStore.RegisteredSpanNamesForCollection.Count); + + sampleStore.RegisterSpanNamesForCollection(new List() { NOT_REGISTERED_SPAN_NAME }); + + Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection); + Assert.Contains(NOT_REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection); + Assert.Equal(2, sampleStore.RegisteredSpanNamesForCollection.Count); + + sampleStore.UnregisterSpanNamesForCollection(new List() { NOT_REGISTERED_SPAN_NAME }); + + Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection); + Assert.Equal(1, sampleStore.RegisteredSpanNamesForCollection.Count); + } + + [Fact] + public void RegisterSpanNamesViaSpanBuilderOption() + { + Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection); + Assert.Equal(1, sampleStore.RegisteredSpanNamesForCollection.Count); + + CreateSampledSpan(NOT_REGISTERED_SPAN_NAME).End(EndSpanOptions.Builder().SetSampleToLocalSpanStore(true).Build()); + + Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection); + Assert.Contains(NOT_REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection); + Assert.Equal(2, sampleStore.RegisteredSpanNamesForCollection.Count); + + } + + [Fact] + public void AddSpansWithRegisteredNamesInAllErrorBuckets() + { + AddSpanNameToAllErrorBuckets(REGISTERED_SPAN_NAME); + IDictionary perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary; + Assert.Equal(1, perSpanNameSummary.Count); + IDictionary errorBucketsSummaries = perSpanNameSummary[REGISTERED_SPAN_NAME].NumbersOfErrorSampledSpans; + var ccCount = Enum.GetValues(typeof(CanonicalCode)).Cast().Count(); + Assert.Equal(ccCount - 1, errorBucketsSummaries.Count); + foreach (var it in errorBucketsSummaries) + { + Assert.Equal(2, it.Value); + } + } + + [Fact] + public void AddSpansWithoutRegisteredNamesInAllErrorBuckets() + { + AddSpanNameToAllErrorBuckets(NOT_REGISTERED_SPAN_NAME); + IDictionary perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary; + Assert.Equal(1, perSpanNameSummary.Count); + Assert.False(perSpanNameSummary.ContainsKey(NOT_REGISTERED_SPAN_NAME)); + } + + [Fact] + public void GetErrorSampledSpans() + { + Span span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(10); + span.End(EndSpanOptions.Builder().SetStatus(Status.Cancelled).Build()); + var samples = + sampleStore.GetErrorSampledSpans( + SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, CanonicalCode.Cancelled, 0)); + Assert.Single(samples); + Assert.Contains(span.ToSpanData(), samples); + } + + [Fact] + public void GetErrorSampledSpans_MaxSpansToReturn() + { + Span span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(10); + span1.End(EndSpanOptions.Builder().SetStatus(Status.Cancelled).Build()); + // Advance time to allow other spans to be sampled. + interval += TimeSpan.FromSeconds(5); + Span span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(10); + span2.End(EndSpanOptions.Builder().SetStatus(Status.Cancelled).Build()); + var samples = + sampleStore.GetErrorSampledSpans( + SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, CanonicalCode.Cancelled, 1)); + Assert.Single(samples); + // No order guaranteed so one of the spans should be in the list. + Assert.True(samples.Contains(span1.ToSpanData()) || samples.Contains(span2.ToSpanData())); + } + + [Fact] + public void GetErrorSampledSpans_NullCode() + { + Span span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(10); + span1.End(EndSpanOptions.Builder().SetStatus(Status.Cancelled).Build()); + Span span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(10); + span2.End(EndSpanOptions.Builder().SetStatus(Status.Unknown).Build()); + var samples = + sampleStore.GetErrorSampledSpans(SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, null, 0)); + Assert.Equal(2, samples.Count()); + Assert.Contains(span1.ToSpanData(), samples); + Assert.Contains(span2.ToSpanData(), samples); + } + + [Fact] + public void GetErrorSampledSpans_NullCode_MaxSpansToReturn() + { + Span span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(10); + span1.End(EndSpanOptions.Builder().SetStatus(Status.Cancelled).Build()); + Span span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(10); + span2.End(EndSpanOptions.Builder().SetStatus(Status.Unknown).Build()); + var samples = + sampleStore.GetErrorSampledSpans(SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, null, 1)); + Assert.Single(samples); + Assert.True(samples.Contains(span1.ToSpanData()) || samples.Contains(span2.ToSpanData())); + } + + [Fact] + public void GetLatencySampledSpans() + { + Span span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(200); // 20 microseconds + span.End(); + var samples = + sampleStore.GetLatencySampledSpans( + SampledSpanStoreLatencyFilter.Create( + REGISTERED_SPAN_NAME, + TimeSpan.FromTicks(150), + TimeSpan.FromTicks(250), + 0)); + Assert.Single(samples); + Assert.Contains(span.ToSpanData(), samples); + } + + [Fact] + public void GetLatencySampledSpans_ExclusiveUpperBound() + { + Span span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(200); // 20 microseconds + span.End(); + var samples = + sampleStore.GetLatencySampledSpans( + SampledSpanStoreLatencyFilter.Create( + REGISTERED_SPAN_NAME, + TimeSpan.FromTicks(150), + TimeSpan.FromTicks(200), + 0)); + Assert.Empty(samples); + } + + [Fact] + public void GetLatencySampledSpans_InclusiveLowerBound() + { + Span span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(200); // 20 microseconds + span.End(); + var samples = + sampleStore.GetLatencySampledSpans( + SampledSpanStoreLatencyFilter.Create( + REGISTERED_SPAN_NAME, + TimeSpan.FromTicks(150), + TimeSpan.FromTicks(250), + 0)); + Assert.Single(samples); + Assert.Contains(span.ToSpanData(), samples); + } + + [Fact] + public void GetLatencySampledSpans_QueryBetweenMultipleBuckets() + { + Span span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(200); // 20 microseconds + span1.End(); + // Advance time to allow other spans to be sampled. + interval += TimeSpan.FromSeconds(5); + Span span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(2000); // 200 microseconds + span2.End(); + var samples = + sampleStore.GetLatencySampledSpans( + SampledSpanStoreLatencyFilter.Create( + REGISTERED_SPAN_NAME, + TimeSpan.FromTicks(150), + TimeSpan.FromTicks(2500), + 0)); + Assert.Equal(2, samples.Count()); + Assert.Contains(span1.ToSpanData(), samples); + Assert.Contains(span2.ToSpanData(), samples); + } + + [Fact] + public void GetLatencySampledSpans_MaxSpansToReturn() + { + Span span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(200); // 20 microseconds + span1.End(); + // Advance time to allow other spans to be sampled. + interval += TimeSpan.FromSeconds(5); + Span span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval += TimeSpan.FromTicks(2000); // 200 microseconds + span2.End(); + var samples = + sampleStore.GetLatencySampledSpans( + SampledSpanStoreLatencyFilter.Create( + REGISTERED_SPAN_NAME, + TimeSpan.FromTicks(150), + TimeSpan.FromTicks(2500), + 1)); + Assert.Single(samples); + Assert.Contains(span1.ToSpanData(), samples); + } + + [Fact] + public void IgnoreNegativeSpanLatency() + { + Span span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span; + interval -= TimeSpan.FromTicks(200); // 20 microseconds + span.End(); + var samples = + sampleStore.GetLatencySampledSpans( + SampledSpanStoreLatencyFilter.Create(REGISTERED_SPAN_NAME, TimeSpan.Zero, TimeSpan.MaxValue, 0)); + Assert.Empty(samples); + } + + private ISpan CreateSampledSpan(string spanName) + { + return Span.StartSpan( + sampledSpanContext, + recordSpanOptions, + spanName, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + } + + private ISpan CreateNotSampledSpan(string spanName) + { + return Span.StartSpan( + notSampledSpanContext, + recordSpanOptions, + spanName, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + } + + private void AddSpanNameToAllLatencyBuckets(string spanName) + { + foreach (LatencyBucketBoundaries boundaries in LatencyBucketBoundaries.Values) + { + ISpan sampledSpan = CreateSampledSpan(spanName); + ISpan notSampledSpan = CreateNotSampledSpan(spanName); + interval += boundaries.LatencyLower; + sampledSpan.End(); + notSampledSpan.End(); + } + } + + private void AddSpanNameToAllErrorBuckets(String spanName) + { + foreach (CanonicalCode code in Enum.GetValues(typeof(CanonicalCode)).Cast()) + { + if (code != CanonicalCode.Ok) + { + ISpan sampledSpan = CreateSampledSpan(spanName); + ISpan notSampledSpan = CreateNotSampledSpan(spanName); + interval += TimeSpan.FromTicks(10); + sampledSpan.End(EndSpanOptions.Builder().SetStatus(code.ToStatus()).Build()); + notSampledSpan.End(EndSpanOptions.Builder().SetStatus(code.ToStatus()).Build()); + } + } + } + + class TestStartEndHandler : IStartEndHandler + { + InProcessSampledSpanStore sampleStore; + + public TestStartEndHandler(InProcessSampledSpanStore store) + { + sampleStore = store; + } + + public void OnStart(ISpan span) + { + // Do nothing. + } + + public void OnEnd(ISpan span) + { + sampleStore.ConsiderForSampling(span); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/NoopRunningSpanStoreTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/NoopRunningSpanStoreTest.cs new file mode 100644 index 000000000..8452fc730 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/NoopRunningSpanStoreTest.cs @@ -0,0 +1,47 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using System; + using System.Collections.Generic; + using Xunit; + + public class NoopRunningSpanStoreTest + { + private readonly IRunningSpanStore runningSpanStore = ExportComponent.NewNoopExportComponent.RunningSpanStore; + + [Fact] + public void NoopRunningSpanStore_GetSummary() + { + IRunningSpanStoreSummary summary = runningSpanStore.Summary; + Assert.Empty(summary.PerSpanNameSummary); + } + + [Fact] + public void NoopRunningSpanStore_GetRunningSpans_DisallowsNull() + { + Assert.Throws(() => runningSpanStore.GetRunningSpans(null)); + } + + [Fact] + public void NoopRunningSpanStore_GetRunningSpans() + { + var runningSpans = runningSpanStore.GetRunningSpans(RunningSpanStoreFilter.Create("TestSpan", 0)); + Assert.Empty(runningSpans); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/NoopSampledSpanStoreTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/NoopSampledSpanStoreTest.cs new file mode 100644 index 000000000..ba351d92f --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/NoopSampledSpanStoreTest.cs @@ -0,0 +1,93 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using System; + using System.Collections.Generic; + using Xunit; + + public class NoopSampledSpanStoreTest + { + // @Rule public final ExpectedException thrown = ExpectedException.none(); + private static readonly ISampledPerSpanNameSummary EMPTY_PER_SPAN_NAME_SUMMARY = + SampledPerSpanNameSummary.Create(new Dictionary(), new Dictionary()); + + [Fact] + public void NoopSampledSpanStore_RegisterUnregisterAndGetSummary() + { + // should return empty before register + ISampledSpanStore sampledSpanStore = + ExportComponent.NewNoopExportComponent.SampledSpanStore; + ISampledSpanStoreSummary summary = sampledSpanStore.Summary; + Assert.Empty(summary.PerSpanNameSummary); + + // should return non-empty summaries with zero latency/error sampled spans after register + sampledSpanStore.RegisterSpanNamesForCollection( + new List() { "TestSpan1", "TestSpan2", "TestSpan3" }); + summary = sampledSpanStore.Summary; + Assert.Equal(3, summary.PerSpanNameSummary.Count); + Assert.Contains(summary.PerSpanNameSummary, (item) => + { + return (item.Key == "TestSpan1" || item.Key == "TestSpan2" || item.Key == "TestSpan3") && + item.Value.Equals(EMPTY_PER_SPAN_NAME_SUMMARY); + }); + + // should unregister specific spanNames + sampledSpanStore.UnregisterSpanNamesForCollection(new List() { "TestSpan1", "TestSpan3" }); + summary = sampledSpanStore.Summary; + Assert.Equal(1, summary.PerSpanNameSummary.Count); + Assert.Contains(summary.PerSpanNameSummary, (item) => + { + return (item.Key == "TestSpan2") && item.Value.Equals(EMPTY_PER_SPAN_NAME_SUMMARY); + }); + + } + + [Fact] + public void NoopSampledSpanStore_GetLatencySampledSpans() + { + ISampledSpanStore sampledSpanStore = ExportComponentBase.NewNoopExportComponent.SampledSpanStore; + var latencySampledSpans = + sampledSpanStore.GetLatencySampledSpans( + SampledSpanStoreLatencyFilter.Create("TestLatencyFilter", TimeSpan.Zero, TimeSpan.Zero, 0)); + Assert.Empty(latencySampledSpans); + } + + [Fact] + public void NoopSampledSpanStore_GetErrorSampledSpans() + { + ISampledSpanStore sampledSpanStore = ExportComponentBase.NewNoopExportComponent.SampledSpanStore; + var errorSampledSpans = + sampledSpanStore.GetErrorSampledSpans( + SampledSpanStoreErrorFilter.Create("TestErrorFilter", null, 0)); + Assert.Empty(errorSampledSpans); + } + + [Fact] + public void NoopSampledSpanStore_GetRegisteredSpanNamesForCollection() + { + ISampledSpanStore sampledSpanStore = ExportComponentBase.NewNoopExportComponent.SampledSpanStore; + sampledSpanStore.RegisterSpanNamesForCollection(new List() { "TestSpan3", "TestSpan4" }); + ISet registeredSpanNames = sampledSpanStore.RegisteredSpanNamesForCollection; + Assert.Equal(2, registeredSpanNames.Count); + Assert.Contains(registeredSpanNames, (item) => + { + return (item == "TestSpan3" || item == "TestSpan4"); + }); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/SpanDataTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/SpanDataTest.cs new file mode 100644 index 000000000..eb8b80357 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/SpanDataTest.cs @@ -0,0 +1,299 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using System.Collections.Generic; + using OpenCensus.Common; + using OpenCensus.Trace.Internal; + using Xunit; + + public class SpanDataTest + { + private static readonly Timestamp startTimestamp = Timestamp.Create(123, 456); + private static readonly Timestamp eventTimestamp1 = Timestamp.Create(123, 457); + private static readonly Timestamp eventTimestamp2 = Timestamp.Create(123, 458); + private static readonly Timestamp eventTimestamp3 = Timestamp.Create(123, 459); + private static readonly Timestamp endTimestamp = Timestamp.Create(123, 460); + private static readonly string SPAN_NAME = "MySpanName"; + private static readonly string ANNOTATION_TEXT = "MyAnnotationText"; + private static readonly IAnnotation annotation = Annotation.FromDescription(ANNOTATION_TEXT); + // private static readonly NetworkEvent recvNetworkEvent = + // NetworkEvent.Builder(NetworkEvent.Type.RECV, 1).build(); + // private static readonly NetworkEvent sentNetworkEvent = + // NetworkEvent.Builder(NetworkEvent.Type.SENT, 1).build(); + private static readonly IMessageEvent recvMessageEvent = MessageEvent.Builder(MessageEventType.Received, 1).Build(); + private static readonly IMessageEvent sentMessageEvent = MessageEvent.Builder(MessageEventType.Sent, 1).Build(); + private static readonly Status status = Status.DeadlineExceeded.WithDescription("TooSlow"); + private static readonly SpanKind kind = SpanKind.Client; + private static readonly int CHILD_SPAN_COUNT = 13; + private readonly IRandomGenerator random = new RandomGenerator(1234); + private readonly ISpanContext spanContext; + private readonly ISpanId parentSpanId; + private readonly IDictionary attributesMap = new Dictionary(); + private readonly List> annotationsList = new List>(); + // private readonly List> networkEventsList = + // new List>(); + private readonly List> messageEventsList = new List>(); + private readonly List linksList = new List(); + + private IAttributes attributes; + private ITimedEvents annotations; + // private TimedEvents networkEvents; + private ITimedEvents messageEvents; + private LinkList links; + + public SpanDataTest() + { + spanContext = SpanContext.Create(TraceId.GenerateRandomId(random), SpanId.GenerateRandomId(random), TraceOptions.Default, Tracestate.Empty); + parentSpanId = SpanId.GenerateRandomId(random); + + attributesMap.Add("MyAttributeKey1", AttributeValue.LongAttributeValue(10)); + attributesMap.Add("MyAttributeKey2", AttributeValue.BooleanAttributeValue(true)); + attributes = Attributes.Create(attributesMap, 1); + + annotationsList.Add(TimedEvent.Create(eventTimestamp1, annotation)); + annotationsList.Add(TimedEvent.Create(eventTimestamp3, annotation)); + annotations = TimedEvents.Create(annotationsList, 2); + + // networkEventsList.add(SpanData.TimedEvent.Create(eventTimestamp1, recvNetworkEvent)); + // networkEventsList.add(SpanData.TimedEvent.Create(eventTimestamp2, sentNetworkEvent)); + // networkEvents = TimedEvents.Create(networkEventsList, 3); + + messageEventsList.Add(TimedEvent.Create(eventTimestamp1, recvMessageEvent)); + messageEventsList.Add(TimedEvent.Create(eventTimestamp2, sentMessageEvent)); + messageEvents = TimedEvents.Create(messageEventsList, 3); + + linksList.Add(Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan)); + links = LinkList.Create(linksList, 0); + } + + [Fact] + public void SpanData_AllValues() + { + ISpanData spanData = + SpanData.Create( + spanContext, + parentSpanId, + true, + SPAN_NAME, + startTimestamp, + attributes, + annotations, + messageEvents, + links, + CHILD_SPAN_COUNT, + status, + kind, + endTimestamp); + Assert.Equal(spanContext, spanData.Context); + Assert.Equal(parentSpanId, spanData.ParentSpanId); + Assert.True(spanData.HasRemoteParent); + Assert.Equal(SPAN_NAME, spanData.Name); + Assert.Equal(startTimestamp, spanData.StartTimestamp); + Assert.Equal(attributes, spanData.Attributes); + Assert.Equal(annotations, spanData.Annotations); + Assert.Equal(messageEvents, spanData.MessageEvents); + Assert.Equal(links, spanData.Links); + Assert.Equal(CHILD_SPAN_COUNT, spanData.ChildSpanCount); + Assert.Equal(status, spanData.Status); + Assert.Equal(endTimestamp, spanData.EndTimestamp); + } + + // [Fact] + // public void SpanData_Create_Compatibility() + // { + // SpanData spanData = + // SpanData.Create( + // spanContext, + // parentSpanId, + // true, + // SPAN_NAME, + // startTimestamp, + // attributes, + // annotations, + // networkEvents, + // links, + // CHILD_SPAN_COUNT, + // status, + // endTimestamp); + // Assert.Equal(spanData.getContext()).isEqualTo(spanContext); + // Assert.Equal(spanData.getParentSpanId()).isEqualTo(parentSpanId); + // Assert.Equal(spanData.getHasRemoteParent()).isTrue(); + // Assert.Equal(spanData.getName()).isEqualTo(SPAN_NAME); + // Assert.Equal(spanData.getStartTimestamp()).isEqualTo(startTimestamp); + // Assert.Equal(spanData.getAttributes()).isEqualTo(attributes); + // Assert.Equal(spanData.getAnnotations()).isEqualTo(annotations); + // Assert.Equal(spanData.getNetworkEvents()).isEqualTo(networkEvents); + // Assert.Equal(spanData.getMessageEvents()).isEqualTo(messageEvents); + // Assert.Equal(spanData.getLinks()).isEqualTo(links); + // Assert.Equal(spanData.getChildSpanCount()).isEqualTo(CHILD_SPAN_COUNT); + // Assert.Equal(spanData.getStatus()).isEqualTo(status); + // Assert.Equal(spanData.getEndTimestamp()).isEqualTo(endTimestamp); + // } + + [Fact] + public void SpanData_RootActiveSpan() + { + ISpanData spanData = + SpanData.Create( + spanContext, + null, + null, + SPAN_NAME, + startTimestamp, + attributes, + annotations, + messageEvents, + links, + null, + null, + kind, + null); + Assert.Equal(spanContext, spanData.Context); + Assert.Null(spanData.ParentSpanId); + Assert.Null(spanData.HasRemoteParent); + Assert.Equal(SPAN_NAME, spanData.Name); + Assert.Equal(startTimestamp, spanData.StartTimestamp); + Assert.Equal(attributes, spanData.Attributes); + Assert.Equal(annotations, spanData.Annotations); + Assert.Equal(messageEvents, spanData.MessageEvents); + Assert.Equal(links, spanData.Links); + Assert.Null(spanData.ChildSpanCount); + Assert.Null(spanData.Status); + Assert.Null(spanData.EndTimestamp); + } + + [Fact] + public void SpanData_AllDataEmpty() + { + ISpanData spanData = + SpanData.Create( + spanContext, + parentSpanId, + false, + SPAN_NAME, + startTimestamp, + Attributes.Create(new Dictionary(), 0), + TimedEvents.Create(new List>(), 0), + TimedEvents.Create(new List>(), 0), + LinkList.Create(new List(), 0), + 0, + status, + kind, + endTimestamp); + + Assert.Equal(spanContext, spanData.Context); + Assert.Equal(parentSpanId, spanData.ParentSpanId); + Assert.False(spanData.HasRemoteParent); + Assert.Equal(SPAN_NAME, spanData.Name); + Assert.Equal(startTimestamp, spanData.StartTimestamp); + Assert.Empty(spanData.Attributes.AttributeMap); + Assert.Empty(spanData.Annotations.Events); + Assert.Empty(spanData.MessageEvents.Events); + Assert.Empty(spanData.Links.Links); + Assert.Equal(0, spanData.ChildSpanCount); + Assert.Equal(status, spanData.Status); + Assert.Equal(endTimestamp, spanData.EndTimestamp); + } + + [Fact] + public void SpanDataEquals() + { + ISpanData allSpanData1 = + SpanData.Create( + spanContext, + parentSpanId, + false, + SPAN_NAME, + startTimestamp, + attributes, + annotations, + messageEvents, + links, + CHILD_SPAN_COUNT, + status, + kind, + endTimestamp); + ISpanData allSpanData2 = + SpanData.Create( + spanContext, + parentSpanId, + false, + SPAN_NAME, + startTimestamp, + attributes, + annotations, + messageEvents, + links, + CHILD_SPAN_COUNT, + status, + kind, + endTimestamp); + ISpanData emptySpanData = + SpanData.Create( + spanContext, + parentSpanId, + false, + SPAN_NAME, + startTimestamp, + Attributes.Create(new Dictionary(), 0), + TimedEvents.Create(new List>(), 0), + TimedEvents.Create(new List>(), 0), + LinkList.Create(new List(), 0), + 0, + status, + kind, + endTimestamp); + + Assert.Equal(allSpanData1, allSpanData2); + Assert.NotEqual(emptySpanData, allSpanData1); + Assert.NotEqual(emptySpanData, allSpanData2); + + } + + [Fact] + public void SpanData_ToString() + { + string spanDataString = + SpanData.Create( + spanContext, + parentSpanId, + false, + SPAN_NAME, + startTimestamp, + attributes, + annotations, + messageEvents, + links, + CHILD_SPAN_COUNT, + status, + kind, + endTimestamp) + .ToString(); + Assert.Contains(spanContext.ToString(), spanDataString); + Assert.Contains(parentSpanId.ToString(), spanDataString); + Assert.Contains(SPAN_NAME, spanDataString); + Assert.Contains(startTimestamp.ToString(), spanDataString); + Assert.Contains(attributes.ToString(), spanDataString); + Assert.Contains(annotations.ToString(), spanDataString); + Assert.Contains(messageEvents.ToString(), spanDataString); + Assert.Contains(links.ToString(), spanDataString); + Assert.Contains(status.ToString(), spanDataString); + Assert.Contains(endTimestamp.ToString(), spanDataString); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Export/SpanExporterTest.cs b/test/OpenCensus.Tests/Impl/Trace/Export/SpanExporterTest.cs new file mode 100644 index 000000000..0f0df21d2 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Export/SpanExporterTest.cs @@ -0,0 +1,218 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Export.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Moq; + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Testing.Export; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using Xunit; + + public class SpanExporterTest + { + private static readonly String SPAN_NAME_1 = "MySpanName/1"; + private static readonly String SPAN_NAME_2 = "MySpanName/2"; + private readonly RandomGenerator random = new RandomGenerator(1234); + private readonly ISpanContext sampledSpanContext; + private readonly ISpanContext notSampledSpanContext; + private readonly ISpanExporter spanExporter = SpanExporter.Create(4, Duration.Create(1, 0)); + private readonly IRunningSpanStore runningSpanStore = new InProcessRunningSpanStore(); + private readonly IStartEndHandler startEndHandler; + private SpanOptions recordSpanOptions = SpanOptions.RecordEvents; + private readonly TestHandler serviceHandler = new TestHandler(); + private IHandler mockServiceHandler = Mock.Of(); + + public SpanExporterTest() + { + sampledSpanContext = SpanContext.Create(TraceId.GenerateRandomId(random), SpanId.GenerateRandomId(random), TraceOptions.Builder().SetIsSampled(true).Build(), Tracestate.Empty); + notSampledSpanContext = SpanContext.Create(TraceId.GenerateRandomId(random), SpanId.GenerateRandomId(random), TraceOptions.Default, Tracestate.Empty); + startEndHandler = new StartEndHandler(spanExporter, runningSpanStore, null, new SimpleEventQueue()); + + spanExporter.RegisterHandler("test.service", serviceHandler); + } + + private Span CreateSampledEndedSpan(string spanName) + { + ISpan span = + Span.StartSpan( + sampledSpanContext, + recordSpanOptions, + spanName, + null, + false, + TraceParams.Default, + startEndHandler, + null); + span.End(); + return span as Span; + } + + private Span CreateNotSampledEndedSpan(string spanName) + { + ISpan span = + Span.StartSpan( + notSampledSpanContext, + recordSpanOptions, + spanName, + null, + false, + TraceParams.Default, + startEndHandler, + null); + span.End(); + return span as Span; + } + + [Fact] + public void ExportDifferentSampledSpans() + { + Span span1 = CreateSampledEndedSpan(SPAN_NAME_1); + Span span2 = CreateSampledEndedSpan(SPAN_NAME_2); + var exported = serviceHandler.WaitForExport(2); + Assert.Equal(2, exported.Count()); + Assert.Contains(span1.ToSpanData(), exported); + Assert.Contains(span2.ToSpanData(), exported); + } + + [Fact] + public void ExportMoreSpansThanTheBufferSize() + { + Span span1 = CreateSampledEndedSpan(SPAN_NAME_1); + Span span2 = CreateSampledEndedSpan(SPAN_NAME_1); + Span span3 = CreateSampledEndedSpan(SPAN_NAME_1); + Span span4 = CreateSampledEndedSpan(SPAN_NAME_1); + Span span5 = CreateSampledEndedSpan(SPAN_NAME_1); + Span span6 = CreateSampledEndedSpan(SPAN_NAME_1); + var exported = serviceHandler.WaitForExport(6); + Assert.Equal(6, exported.Count()); + Assert.Contains(span1.ToSpanData(), exported); + Assert.Contains(span2.ToSpanData(), exported); + Assert.Contains(span3.ToSpanData(), exported); + Assert.Contains(span4.ToSpanData(), exported); + Assert.Contains(span5.ToSpanData(), exported); + Assert.Contains(span6.ToSpanData(), exported); + + } + + [Fact] + public void InterruptWorkerThreadStops() + { + + Thread serviceExporterThread = ((SpanExporter)spanExporter).ServiceExporterThread; + spanExporter.Dispose(); + // serviceExporterThread.Interrupt(); + // Test that the worker thread will stop. + serviceExporterThread.Join(); + } + + [Fact] + public void ServiceHandlerThrowsException() + { + var mockHandler = Mock.Get(mockServiceHandler); + mockHandler.Setup((h) => h.ExportAsync(It.IsAny>())).Throws(new ArgumentException("No export for you.")); + // doThrow(new IllegalArgumentException("No export for you.")) + // .when(mockServiceHandler) + // .export(anyListOf(SpanData)); + spanExporter.RegisterHandler("mock.service", mockServiceHandler); + Span span1 = CreateSampledEndedSpan(SPAN_NAME_1); + var exported = serviceHandler.WaitForExport(1); + Assert.Single(exported); + Assert.Contains(span1.ToSpanData(), exported); + // assertThat(exported).containsExactly(span1.toSpanData()); + // Continue to export after the exception was received. + Span span2 = CreateSampledEndedSpan(SPAN_NAME_1); + exported = serviceHandler.WaitForExport(1); + Assert.Single(exported); + Assert.Contains(span2.ToSpanData(), exported); + // assertThat(exported).containsExactly(span2.toSpanData()); + } + + [Fact] + public void ExportSpansToMultipleServices() + { + TestHandler serviceHandler2 = new TestHandler(); + spanExporter.RegisterHandler("test.service2", serviceHandler2); + Span span1 = CreateSampledEndedSpan(SPAN_NAME_1); + Span span2 = CreateSampledEndedSpan(SPAN_NAME_2); + var exported1 = serviceHandler.WaitForExport(2); + var exported2 = serviceHandler2.WaitForExport(2); + Assert.Equal(2, exported1.Count()); + Assert.Contains(span1.ToSpanData(), exported1); + Assert.Contains(span2.ToSpanData(), exported1); + Assert.Equal(2, exported2.Count()); + Assert.Contains(span1.ToSpanData(), exported2); + Assert.Contains(span2.ToSpanData(), exported2); + } + + [Fact] + public void ExportNotSampledSpans() + { + Span span1 = CreateNotSampledEndedSpan(SPAN_NAME_1); + Span span2 = CreateSampledEndedSpan(SPAN_NAME_2); + // Spans are recorded and exported in the same order as they are ended, we test that a non + // sampled span is not exported by creating and ending a sampled span after a non sampled span + // and checking that the first exported span is the sampled span (the non sampled did not get + // exported). + var exported = serviceHandler.WaitForExport(1); + // Need to check this because otherwise the variable span1 is unused, other option is to not + // have a span1 variable. + Assert.Single(exported); + Assert.DoesNotContain(span1.ToSpanData(), exported); + Assert.Contains(span2.ToSpanData(), exported); + } + + [Fact] + public async Task ExportAsyncCallsAllHandlers() + { + var exporter = SpanExporter.Create(4, Duration.Create(1, 0)); + + var handler1 = new Mock(); + var handler2 = new Mock(); + + + exporter.RegisterHandler("first", handler1.Object); + exporter.RegisterHandler("second", handler2.Object); + + var span1 = new Mock(); + var span2 = new Mock(); + + await exporter.ExportAsync(new ISpanData[] { span1.Object, span2.Object }, CancellationToken.None); + + Assert.Single(handler1.Invocations); + var args = (IEnumerable)handler1.Invocations.First().Arguments.First(); + + + handler1.Verify(c => c.ExportAsync(It.Is>( + (x) => x.Where((s) => s == span1.Object).Count() > 0 && + x.Where((s) => s == span2.Object).Count() > 0 && + x.Count() == 2))); + + handler2.Verify(c => c.ExportAsync(It.Is>( + (x) => x.Where((s) => s == span1.Object).Count() > 0 && + x.Where((s) => s == span2.Object).Count() > 0 && + x.Count() == 2))); + } + } + +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Internal/ConcurrentIntrusiveListTest.cs b/test/OpenCensus.Tests/Impl/Trace/Internal/ConcurrentIntrusiveListTest.cs new file mode 100644 index 000000000..7a52b74b2 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Internal/ConcurrentIntrusiveListTest.cs @@ -0,0 +1,112 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Internal.Test +{ + using System; + using OpenCensus.Utils; + using Xunit; + + public class ConcurrentIntrusiveListTest + { + private readonly ConcurrentIntrusiveList intrusiveList = new ConcurrentIntrusiveList(); + + [Fact] + public void EmptyList() + { + Assert.Equal(0, intrusiveList.Count); + Assert.Empty(intrusiveList.Copy()); + } + + [Fact] + public void AddRemoveAdd_SameElement() + { + FakeElement element = new FakeElement(); + intrusiveList.AddElement(element); + Assert.Equal(1, intrusiveList.Count); + intrusiveList.RemoveElement(element); + Assert.Equal(0, intrusiveList.Count); + intrusiveList.AddElement(element); + Assert.Equal(1, intrusiveList.Count); + } + + [Fact] + public void addAndRemoveElements() + { + FakeElement element1 = new FakeElement(); + FakeElement element2 = new FakeElement(); + FakeElement element3 = new FakeElement(); + intrusiveList.AddElement(element1); + intrusiveList.AddElement(element2); + intrusiveList.AddElement(element3); + Assert.Equal(3, intrusiveList.Count); + var copy = intrusiveList.Copy(); + Assert.Equal(element3, copy[0]); + Assert.Equal(element2, copy[1]); + Assert.Equal(element1, copy[2]); + // Remove element from the middle of the list. + intrusiveList.RemoveElement(element2); + Assert.Equal(2, intrusiveList.Count); + copy = intrusiveList.Copy(); + Assert.Equal(element3, copy[0]); + Assert.Equal(element1, copy[1]); + // Remove element from the tail of the list. + intrusiveList.RemoveElement(element1); + Assert.Equal(1, intrusiveList.Count); + copy = intrusiveList.Copy(); + Assert.Equal(element3, copy[0]); + + intrusiveList.AddElement(element1); + Assert.Equal(2, intrusiveList.Count); + copy = intrusiveList.Copy(); + Assert.Equal(element1, copy[0]); + Assert.Equal(element3, copy[1]); + // Remove element from the head of the list when there are other elements after. + intrusiveList.RemoveElement(element1); + Assert.Equal(1, intrusiveList.Count); + copy = intrusiveList.Copy(); + Assert.Equal(element3, copy[0]); + // Remove element from the head of the list when no more other elements in the list. + intrusiveList.RemoveElement(element3); + Assert.Equal(0, intrusiveList.Count); + Assert.Empty(intrusiveList.Copy()); + } + + [Fact] + public void AddAlreadyAddedElement() + { + FakeElement element = new FakeElement(); + intrusiveList.AddElement(element); + + Assert.Throws(() => intrusiveList.AddElement(element)); + } + + [Fact] + public void removeNotAddedElement() + { + FakeElement element = new FakeElement(); + Assert.Throws(() => intrusiveList.RemoveElement(element)); + } + + + private sealed class FakeElement : IElement + { + public FakeElement Next { get; set; } + + public FakeElement Previous { get; set; } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/LinkTest.cs b/test/OpenCensus.Tests/Impl/Trace/LinkTest.cs new file mode 100644 index 000000000..08f35cd4f --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/LinkTest.cs @@ -0,0 +1,114 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System.Collections.Generic; + using OpenCensus.Trace.Internal; + using OpenCensus.Utils; + using Xunit; + + public class LinkTest + { + private readonly IDictionary attributesMap = new Dictionary(); + private readonly IRandomGenerator random = new RandomGenerator(1234); + private readonly ISpanContext spanContext; + + + public LinkTest() + { + spanContext = SpanContext.Create(TraceId.GenerateRandomId(random), SpanId.GenerateRandomId(random), TraceOptions.Default, Tracestate.Empty); ; + attributesMap.Add("MyAttributeKey0", AttributeValue.Create("MyStringAttribute")); + attributesMap.Add("MyAttributeKey1", AttributeValue.Create(10)); + attributesMap.Add("MyAttributeKey2", AttributeValue.Create(true)); + attributesMap.Add("MyAttributeKey3", AttributeValue.Create(0.005)); + } + + [Fact] + public void FromSpanContext_ChildLink() + { + ILink link = Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan); + Assert.Equal(spanContext.TraceId, link.TraceId); + Assert.Equal(spanContext.SpanId, link.SpanId); + Assert.Equal(LinkType.ChildLinkedSpan, link.Type); + } + + [Fact] + public void FromSpanContext_ChildLink_WithAttributes() + { + ILink link = Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan, attributesMap); + Assert.Equal(spanContext.TraceId, link.TraceId); + Assert.Equal(spanContext.SpanId, link.SpanId); + Assert.Equal(LinkType.ChildLinkedSpan, link.Type); + Assert.Equal(attributesMap, link.Attributes); + } + + [Fact] + public void FromSpanContext_ParentLink() + { + ILink link = Link.FromSpanContext(spanContext, LinkType.ParentLinkedSpan); + Assert.Equal(spanContext.TraceId, link.TraceId); + Assert.Equal(spanContext.SpanId, link.SpanId); + Assert.Equal(LinkType.ParentLinkedSpan, link.Type); + } + + [Fact] + public void FromSpanContext_ParentLink_WithAttributes() + { + ILink link = Link.FromSpanContext(spanContext, LinkType.ParentLinkedSpan, attributesMap); + Assert.Equal(spanContext.TraceId, link.TraceId); + Assert.Equal(spanContext.SpanId, link.SpanId); + Assert.Equal(LinkType.ParentLinkedSpan, link.Type); + Assert.Equal(attributesMap, link.Attributes); + } + + [Fact] + public void Link_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // tester + // .addEqualityGroup( + // Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN), + // Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN)) + // .addEqualityGroup( + // Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN), + // Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN)) + // .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.CHILD_LINKED_SPAN)) + // .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.PARENT_LINKED_SPAN)) + // .addEqualityGroup( + // Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap), + // Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap)); + // tester.testEquals(); + + + } + + [Fact] + public void Link_ToString() + { + ILink link = Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan, attributesMap); + Assert.Contains(spanContext.TraceId.ToString(), link.ToString()); + Assert.Contains(spanContext.SpanId.ToString(), link.ToString()); + Assert.Contains("ChildLinkedSpan", link.ToString()); + Assert.Contains(Collections.ToString(attributesMap), link.ToString()); + link = Link.FromSpanContext(spanContext, LinkType.ParentLinkedSpan, attributesMap); + Assert.Contains(spanContext.TraceId.ToString(), link.ToString()); + Assert.Contains(spanContext.SpanId.ToString(), spanContext.SpanId.ToString()); + Assert.Contains("ParentLinkedSpan", link.ToString()); + Assert.Contains(Collections.ToString(attributesMap), link.ToString()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/MessageEventTest.cs b/test/OpenCensus.Tests/Impl/Trace/MessageEventTest.cs new file mode 100644 index 000000000..973f6e20e --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/MessageEventTest.cs @@ -0,0 +1,82 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class MessageEventTest + { + + [Fact] + public void BuildMessageEvent_WithRequiredFields() + { + IMessageEvent messageEvent = MessageEvent.Builder(MessageEventType.Sent, 1L).Build(); + Assert.Equal(MessageEventType.Sent, messageEvent.Type); + Assert.Equal(1L, messageEvent.MessageId); + Assert.Equal(0L, messageEvent.UncompressedMessageSize); + } + + [Fact] + public void BuildMessageEvent_WithUncompressedMessageSize() + { + IMessageEvent messageEvent = + MessageEvent.Builder(MessageEventType.Sent, 1L).SetUncompressedMessageSize(123L).Build(); + Assert.Equal(MessageEventType.Sent, messageEvent.Type); + Assert.Equal(1L, messageEvent.MessageId); + Assert.Equal(123L, messageEvent.UncompressedMessageSize); + } + + [Fact] + public void BuildMessageEvent_WithCompressedMessageSize() + { + IMessageEvent messageEvent = + MessageEvent.Builder(MessageEventType.Sent, 1L).SetCompressedMessageSize(123L).Build(); + Assert.Equal(MessageEventType.Sent, messageEvent.Type); + Assert.Equal(1L, messageEvent.MessageId); + Assert.Equal(123L, messageEvent.CompressedMessageSize); + } + + [Fact] + public void BuildMessageEvent_WithAllValues() + { + IMessageEvent messageEvent = + MessageEvent.Builder(MessageEventType.Received, 1L) + .SetUncompressedMessageSize(123L) + .SetCompressedMessageSize(63L) + .Build(); + Assert.Equal(MessageEventType.Received, messageEvent.Type); + Assert.Equal(1L, messageEvent.MessageId); + Assert.Equal(123L, messageEvent.UncompressedMessageSize); + Assert.Equal(63L, messageEvent.CompressedMessageSize); + } + + [Fact] + public void MessageEvent_ToString() + { + IMessageEvent messageEvent = + MessageEvent.Builder(MessageEventType.Sent, 1L) + .SetUncompressedMessageSize(123L) + .SetCompressedMessageSize(63L) + .Build(); + Assert.Contains("type=Sent", messageEvent.ToString()); + Assert.Contains("messageId=1", messageEvent.ToString()); + Assert.Contains("compressedMessageSize=63", messageEvent.ToString()); + Assert.Contains("uncompressedMessageSize=123", messageEvent.ToString()); + } + } +} + diff --git a/test/OpenCensus.Tests/Impl/Trace/NoopSpan.cs b/test/OpenCensus.Tests/Impl/Trace/NoopSpan.cs new file mode 100644 index 000000000..7c48ad005 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/NoopSpan.cs @@ -0,0 +1,75 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System; + using System.Collections.Generic; + using OpenCensus.Trace.Export; + + public class NoopSpan : SpanBase + { + public NoopSpan(ISpanContext context, SpanOptions options) + : base(context, options) + { + } + + public override DateTimeOffset EndTime { get; } + + public override TimeSpan Latency { get; } + + public override bool IsSampleToLocalSpanStore { get; } + + public override Status Status { get; set; } + + public override SpanKind? Kind { get; set; } + + public override string Name { get; set; } + + public override ISpanId ParentSpanId { get; } + + public override bool HasEnded => true; + + public override void AddAnnotation(string description, IDictionary attributes) + { + } + + public override void AddAnnotation(IAnnotation annotation) + { + } + + public override void AddLink(ILink link) + { + } + + public override void AddMessageEvent(IMessageEvent messageEvent) + { + } + + public override void End(EndSpanOptions options) + { + } + + public override void PutAttributes(IDictionary attributes) + { + } + + public override ISpanData ToSpanData() + { + return null; + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/B3FormatTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/B3FormatTest.cs new file mode 100644 index 000000000..52ff3d0a7 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/B3FormatTest.cs @@ -0,0 +1,217 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Test +{ + using System; + using System.Collections.Generic; + using Xunit; + using Xunit.Abstractions; + + public class B3FormatTest + { + private static readonly string TRACE_ID_BASE16 = "ff000000000000000000000000000041"; + private static readonly ITraceId TRACE_ID = TraceId.FromLowerBase16(TRACE_ID_BASE16); + private static readonly string TRACE_ID_BASE16_EIGHT_BYTES = "0000000000000041"; + private static readonly ITraceId TRACE_ID_EIGHT_BYTES = TraceId.FromLowerBase16("0000000000000000" + TRACE_ID_BASE16_EIGHT_BYTES); + private static readonly string SPAN_ID_BASE16 = "ff00000000000041"; + private static readonly ISpanId SPAN_ID = SpanId.FromLowerBase16(SPAN_ID_BASE16); + private static readonly byte[] TRACE_OPTIONS_BYTES = new byte[] { 1 }; + private static readonly TraceOptions TRACE_OPTIONS = TraceOptions.FromBytes(TRACE_OPTIONS_BYTES); + private readonly B3Format b3Format = new B3Format(); + + + private static readonly Action, string, string> setter = (d, k, v) => d[k] = v; + private static readonly Func, string, IEnumerable> getter = (d, k) => { d.TryGetValue(k, out string v); return new string[] { v }; }; + ITestOutputHelper _output; + + public B3FormatTest(ITestOutputHelper output) + { + _output = output; + + } + + [Fact] + public void Serialize_SampledContext() + { + IDictionary carrier = new Dictionary(); + b3Format.Inject(SpanContext.Create(TRACE_ID, SPAN_ID, TRACE_OPTIONS, Tracestate.Empty), carrier, setter); + ContainsExactly(carrier, new Dictionary() { { B3Format.XB3TraceId, TRACE_ID_BASE16 }, { B3Format.XB3SpanId, SPAN_ID_BASE16 }, { B3Format.XB3Sampled, "1" } }); + } + + [Fact] + public void Serialize_NotSampledContext() + { + IDictionary carrier = new Dictionary(); + var context = SpanContext.Create(TRACE_ID, SPAN_ID, TraceOptions.Default, Tracestate.Empty); + _output.WriteLine(context.ToString()); + b3Format.Inject(context, carrier, setter); + ContainsExactly(carrier, new Dictionary() { { B3Format.XB3TraceId, TRACE_ID_BASE16 }, { B3Format.XB3SpanId, SPAN_ID_BASE16 } }); + } + + [Fact] + public void ParseMissingSampledAndMissingFlag() + { + IDictionary headersNotSampled = new Dictionary(); + headersNotSampled.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + headersNotSampled.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + ISpanContext spanContext = SpanContext.Create(TRACE_ID, SPAN_ID, TraceOptions.Default, Tracestate.Empty); + Assert.Equal(spanContext, b3Format.Extract(headersNotSampled, getter)); + } + + [Fact] + public void ParseSampled() + { + IDictionary headersSampled = new Dictionary(); + headersSampled.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + headersSampled.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + headersSampled.Add(B3Format.XB3Sampled, "1"); + Assert.Equal(SpanContext.Create(TRACE_ID, SPAN_ID, TRACE_OPTIONS, Tracestate.Empty), b3Format.Extract(headersSampled, getter)); + } + + [Fact] + public void ParseZeroSampled() + { + IDictionary headersNotSampled = new Dictionary(); + headersNotSampled.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + headersNotSampled.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + headersNotSampled.Add(B3Format.XB3Sampled, "0"); + Assert.Equal(SpanContext.Create(TRACE_ID, SPAN_ID, TraceOptions.Default, Tracestate.Empty), b3Format.Extract(headersNotSampled, getter)); + } + + [Fact] + public void ParseFlag() + { + IDictionary headersFlagSampled = new Dictionary(); + headersFlagSampled.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + headersFlagSampled.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + headersFlagSampled.Add(B3Format.XB3Flags, "1"); + Assert.Equal(SpanContext.Create(TRACE_ID, SPAN_ID, TRACE_OPTIONS, Tracestate.Empty), b3Format.Extract(headersFlagSampled, getter)); + } + + [Fact] + public void ParseZeroFlag() + { + IDictionary headersFlagNotSampled = new Dictionary(); + headersFlagNotSampled.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + headersFlagNotSampled.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + headersFlagNotSampled.Add(B3Format.XB3Flags, "0"); + Assert.Equal(SpanContext.Create(TRACE_ID, SPAN_ID, TraceOptions.Default, Tracestate.Empty), b3Format.Extract(headersFlagNotSampled, getter)); + } + + [Fact] + public void ParseEightBytesTraceId() + { + IDictionary headersEightBytes = new Dictionary(); + headersEightBytes.Add(B3Format.XB3TraceId, TRACE_ID_BASE16_EIGHT_BYTES); + headersEightBytes.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + headersEightBytes.Add(B3Format.XB3Sampled, "1"); + Assert.Equal(SpanContext.Create(TRACE_ID_EIGHT_BYTES, SPAN_ID, TRACE_OPTIONS, Tracestate.Empty), b3Format.Extract(headersEightBytes, getter)); + } + + [Fact] + public void ParseEightBytesTraceId_NotSampledSpanContext() + { + IDictionary headersEightBytes = new Dictionary(); + headersEightBytes.Add(B3Format.XB3TraceId, TRACE_ID_BASE16_EIGHT_BYTES); + headersEightBytes.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + Assert.Equal(SpanContext.Create(TRACE_ID_EIGHT_BYTES, SPAN_ID, TraceOptions.Default, Tracestate.Empty), b3Format.Extract(headersEightBytes, getter)); + } + + [Fact] + public void ParseInvalidTraceId() + { + IDictionary invalidHeaders = new Dictionary(); + invalidHeaders.Add(B3Format.XB3TraceId, "abcdefghijklmnop"); + invalidHeaders.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + Assert.Throws(() => b3Format.Extract(invalidHeaders, getter)); + } + + [Fact] + public void ParseInvalidTraceId_Size() + { + IDictionary invalidHeaders = new Dictionary(); + invalidHeaders.Add(B3Format.XB3TraceId, "0123456789abcdef00"); + invalidHeaders.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + Assert.Throws(() => b3Format.Extract(invalidHeaders, getter)); + } + + [Fact] + public void ParseMissingTraceId() + { + IDictionary invalidHeaders = new Dictionary(); + invalidHeaders.Add(B3Format.XB3SpanId, SPAN_ID_BASE16); + Assert.Throws(() => b3Format.Extract(invalidHeaders, getter)); + } + + [Fact] + public void ParseInvalidSpanId() + { + IDictionary invalidHeaders = new Dictionary(); + invalidHeaders.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + invalidHeaders.Add(B3Format.XB3SpanId, "abcdefghijklmnop"); + Assert.Throws(() => b3Format.Extract(invalidHeaders, getter)); + } + + [Fact] + public void ParseInvalidSpanId_Size() + { + IDictionary invalidHeaders = new Dictionary(); + invalidHeaders.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + invalidHeaders.Add(B3Format.XB3SpanId, "0123456789abcdef00"); + Assert.Throws(() => b3Format.Extract(invalidHeaders, getter)); + } + + [Fact] + public void ParseMissingSpanId() + { + IDictionary invalidHeaders = new Dictionary(); + invalidHeaders.Add(B3Format.XB3TraceId, TRACE_ID_BASE16); + Assert.Throws(() => b3Format.Extract(invalidHeaders, getter)); + } + + [Fact] + public void Fields_list() + { + ContainsExactly(b3Format.Fields, + new List() { B3Format.XB3TraceId, B3Format.XB3SpanId, B3Format.XB3ParentSpanId, B3Format.XB3Sampled, B3Format.XB3Flags }); + } + + private void ContainsExactly(ISet list, List items) + { + Assert.Equal(items.Count, list.Count); + foreach (var item in items) + { + Assert.Contains(item, list); + } + } + + private void ContainsExactly(IDictionary dict, IDictionary items) + { + foreach (var d in dict) + { + _output.WriteLine(d.Key + "=" + d.Value); + } + + Assert.Equal(items.Count, dict.Count); + foreach (var item in items) + { + Assert.Contains(item, dict); + } + } + } +} + diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatBaseTest.cs new file mode 100644 index 000000000..6d0991203 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatBaseTest.cs @@ -0,0 +1,51 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Test +{ + using System; + using Xunit; + + public class BinaryFormatBaseTest + { + private static readonly IBinaryFormat binaryFormat = BinaryFormatBase.NoopBinaryFormat; + + [Fact] + public void ToByteArray_NullSpanContext() + { + Assert.Throws(() => binaryFormat.ToByteArray(null)); + } + + [Fact] + public void ToByteArray_NotNullSpanContext() + { + Assert.Equal(new byte[0], binaryFormat.ToByteArray(SpanContext.Invalid)); + } + + [Fact] + public void FromByteArray_NullInput() + { + Assert.Throws(() => binaryFormat.FromByteArray(null)); + } + + [Fact] + public void FromByteArray_NotNullInput() + { + Assert.Equal(SpanContext.Invalid, binaryFormat.FromByteArray(new byte[0])); + } + } +} + diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatTest.cs new file mode 100644 index 000000000..d63520108 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/BinaryFormatTest.cs @@ -0,0 +1,156 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Test +{ + using OpenCensus.Trace.Propagation.Implementation; + using System; + using Xunit; + + public class BinaryFormatTest + { + private static readonly byte[] TRACE_ID_BYTES = new byte[] { 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79 }; + private static readonly ITraceId TRACE_ID = TraceId.FromBytes(TRACE_ID_BYTES); + private static readonly byte[] SPAN_ID_BYTES = new byte[] { 97, 98, 99, 100, 101, 102, 103, 104 }; + private static readonly ISpanId SPAN_ID = SpanId.FromBytes(SPAN_ID_BYTES); + private static readonly byte[] TRACE_OPTIONS_BYTES = new byte[] { 1 }; + private static readonly TraceOptions TRACE_OPTIONS = TraceOptions.FromBytes(TRACE_OPTIONS_BYTES); + private static readonly byte[] EXAMPLE_BYTES = + new byte[] {0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, 100,101, 102, 103, 104, 2, 1}; + + private static readonly ISpanContext EXAMPLE_SPAN_CONTEXT = SpanContext.Create(TRACE_ID, SPAN_ID, TRACE_OPTIONS, Tracestate.Empty); + + private readonly BinaryFormat binaryFormat = new BinaryFormat(); + + private void TestSpanContextConversion(ISpanContext spanContext) + { + ISpanContext propagatedBinarySpanContext = binaryFormat.FromByteArray(binaryFormat.ToByteArray(spanContext)); + Assert.Equal(spanContext, propagatedBinarySpanContext); + } + + [Fact] + public void Propagate_SpanContextTracingEnabled() + { + TestSpanContextConversion( + SpanContext.Create(TRACE_ID, SPAN_ID, TraceOptions.Builder().SetIsSampled(true).Build(), Tracestate.Empty)); + } + + [Fact] + public void Propagate_SpanContextNoTracing() + { + TestSpanContextConversion(SpanContext.Create(TRACE_ID, SPAN_ID, TraceOptions.Default, Tracestate.Empty)); + } + + [Fact] + public void ToBinaryValue_NullSpanContext() + { + Assert.Throws(() => binaryFormat.ToByteArray(null)); + } + + [Fact] + public void ToBinaryValue_InvalidSpanContext() + { + Assert.Equal( + new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0}, + binaryFormat.ToByteArray(SpanContext.Invalid)); + + } + + [Fact] + public void FromBinaryValue_BinaryExampleValue() + { + Assert.Equal(EXAMPLE_SPAN_CONTEXT, binaryFormat.FromByteArray(EXAMPLE_BYTES)); + } + + [Fact] + public void FromBinaryValue_NullInput() + { + Assert.Throws(() => binaryFormat.FromByteArray(null)); + } + + [Fact] + public void FromBinaryValue_EmptyInput() + { + + Assert.Throws(() => binaryFormat.FromByteArray(new byte[0])); + } + + [Fact] + public void FromBinaryValue_UnsupportedVersionId() + { + + Assert.Throws(() => binaryFormat.FromByteArray( + new byte[] { + 66, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 97, 98, 99, 100, 101, + 102, 103, 104, 1, + })); + } + + [Fact] + public void FromBinaryValue_UnsupportedFieldIdFirst() + { + Assert.Equal( + SpanContext.Create(TraceId.Invalid, SpanId.Invalid, TraceOptions.Default, Tracestate.Empty), + binaryFormat.FromByteArray( + new byte[] { + 0, 4, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, + 99, 100, 101, 102, 103, 104, 2, 1, + })); + } + + [Fact] + public void FromBinaryValue_UnsupportedFieldIdSecond() + { + Assert.Equal( + SpanContext.Create( + TraceId.FromBytes( + new byte[] { 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79 }), + SpanId.Invalid, + TraceOptions.Default, Tracestate.Empty), + binaryFormat.FromByteArray( + new byte[] { + 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 3, 97, 98, + 99, 100, 101, 102, 103, 104, 2, 1, + })); + + } + + [Fact] + public void FromBinaryValue_ShorterTraceId() + { + + Assert.Throws(() => binaryFormat.FromByteArray( + new byte[] { 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76 })); + } + + [Fact] + public void FromBinaryValue_ShorterSpanId() + { + + Assert.Throws(() => binaryFormat.FromByteArray(new byte[] { 0, 1, 97, 98, 99, 100, 101, 102, 103 })); + } + + [Fact] + public void FromBinaryValue_ShorterTraceOptions() + { + + Assert.Throws(() => binaryFormat.FromByteArray( + new byte[] { + 0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, 100, + 101, 102, 103, 104, 2,})); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentBaseTest.cs new file mode 100644 index 000000000..4df69073b --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentBaseTest.cs @@ -0,0 +1,32 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Test +{ + using OpenCensus.Trace.Propagation.Implementation; + using Xunit; + + public class PropagationComponentBaseTest + { + private readonly IPropagationComponent propagationComponent = PropagationComponentBase.NoopPropagationComponent; + + [Fact] + public void ImplementationOfBinaryFormat() + { + Assert.Equal(BinaryFormat.NoopBinaryFormat, propagationComponent.BinaryFormat); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentTest.cs new file mode 100644 index 000000000..cebe84785 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/PropagationComponentTest.cs @@ -0,0 +1,38 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Test +{ + using OpenCensus.Trace.Propagation.Implementation; + using Xunit; + + public class PropagationComponentTest + { + private readonly DefaultPropagationComponent propagationComponent = new DefaultPropagationComponent(); + + [Fact] + public void ImplementationOfBinary() + { + Assert.IsType(propagationComponent.BinaryFormat); + } + + [Fact] + public void ImplementationOfB3Format() + { + Assert.IsType(propagationComponent.TextFormat); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/SpanContextParseExceptionTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/SpanContextParseExceptionTest.cs new file mode 100644 index 000000000..a49c6493f --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/SpanContextParseExceptionTest.cs @@ -0,0 +1,39 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Test +{ + using System; + using Xunit; + + public class SpanContextParseExceptionTest + { + [Fact] + public void CreateWithMessage() + { + Assert.Equal("my message", new SpanContextParseException("my message").Message); + } + + [Fact] + public void createWithMessageAndCause() + { + Exception cause = new Exception(); + SpanContextParseException parseException = new SpanContextParseException("my message", cause); + Assert.Equal("my message", parseException.Message); + Assert.Equal(cause, parseException.InnerException); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/TextFormatBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/TextFormatBaseTest.cs new file mode 100644 index 000000000..be2009155 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/TextFormatBaseTest.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Propagation.Test +{ + using System; + using Xunit; + + public class TextFormatTest + { + private static readonly ITextFormat textFormat = TextFormatBase.NoopTextFormat; + + [Fact] + public void Inject_NullSpanContext() + { + Assert.Throws(() => textFormat.Inject(null, new object(), (d, k, v) => { })); + } + + [Fact] + public void Inject_NotNullSpanContext_DoesNotFail() + { + textFormat.Inject(SpanContext.Invalid, new object(), (d, k, v) => { }); + } + + [Fact] + public void FromHeaders_NullGetter() + { + Assert.Throws(() => textFormat.Extract(new object(), null)); + } + + [Fact] + public void FromHeaders_NotNullGetter() + { + Assert.Same(SpanContext.Invalid, textFormat.Extract(new object(), (d, k) => null)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Propagation/TraceContextTest.cs b/test/OpenCensus.Tests/Impl/Trace/Propagation/TraceContextTest.cs new file mode 100644 index 000000000..550856da4 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Propagation/TraceContextTest.cs @@ -0,0 +1,56 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Impl.Trace.Propagation +{ + using System.Collections.Generic; + using System.Linq; + using OpenCensus.Trace; + using OpenCensus.Trace.Propagation; + using Xunit; + + public class TraceContextTest + { + [Fact] + public void TraceContextFormatCanParseExampleFromSpec() + { + Dictionary headers = new Dictionary() + { + { "traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01" }, + { "tracestate", "congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01" }, + }; + + var f = new TraceContextFormat(); + var ctx = f.Extract(headers, (h, n) => new string[] { h[n] } ); + + Assert.Equal(TraceId.FromLowerBase16("0af7651916cd43dd8448eb211c80319c"), ctx.TraceId); + Assert.Equal(SpanId.FromLowerBase16("b9c7c989f97918e1"), ctx.SpanId); + Assert.True(ctx.TraceOptions.IsSampled); + + Assert.Equal(2, ctx.Tracestate.Entries.Count()); + + var first = ctx.Tracestate.Entries.First(); + + Assert.Equal("congo", first.Key); + Assert.Equal("lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4", first.Value); + + var last = ctx.Tracestate.Entries.Last(); + + Assert.Equal("rojo", last.Key); + Assert.Equal("00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01", last.Value); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/Sampler/SamplersTest.cs b/test/OpenCensus.Tests/Impl/Trace/Sampler/SamplersTest.cs new file mode 100644 index 000000000..887568df4 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/Sampler/SamplersTest.cs @@ -0,0 +1,288 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Sampler.Test +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using OpenCensus.Trace.Internal; + using OpenCensus.Trace.Test; + using Xunit; + + public class SamplersTest + { + private static readonly String SPAN_NAME = "MySpanName"; + private static readonly int NUM_SAMPLE_TRIES = 1000; + private readonly IRandomGenerator random = new RandomGenerator(1234); + private readonly ITraceId traceId; + private readonly ISpanId parentSpanId; + private readonly ISpanId spanId; + private readonly ISpanContext sampledSpanContext; + private readonly ISpanContext notSampledSpanContext; + private readonly ISpan sampledSpan; + + public SamplersTest() + { + traceId = TraceId.GenerateRandomId(random); + parentSpanId = SpanId.GenerateRandomId(random); + spanId = SpanId.GenerateRandomId(random); + sampledSpanContext = SpanContext.Create(traceId, parentSpanId, TraceOptions.Builder().SetIsSampled(true).Build(), Tracestate.Empty); + notSampledSpanContext = SpanContext.Create(traceId, parentSpanId, TraceOptions.Default, Tracestate.Empty); + sampledSpan = new NoopSpan(sampledSpanContext, SpanOptions.RecordEvents); + } + + [Fact] + public void AlwaysSampleSampler_AlwaysReturnTrue() + { + // Sampled parent. + Assert.True( + Samplers.AlwaysSample + .ShouldSample( + sampledSpanContext, + false, + traceId, + spanId, + "Another name", + new List())); + + // Not sampled parent. + Assert.True( + Samplers.AlwaysSample + .ShouldSample( + notSampledSpanContext, + false, + traceId, + spanId, + "Yet another name", + new List())); + + } + + [Fact] + public void AlwaysSampleSampler_ToString() + { + Assert.Equal("AlwaysSampleSampler", Samplers.AlwaysSample.ToString()); + } + + [Fact] + public void NeverSampleSampler_AlwaysReturnFalse() + { + // Sampled parent. + Assert.False( + Samplers.NeverSample + .ShouldSample( + sampledSpanContext, + false, + traceId, + spanId, + "bar", + new List())); + // Not sampled parent. + Assert.False( + Samplers.NeverSample + .ShouldSample( + notSampledSpanContext, + false, + traceId, + spanId, + "quux", + new List())); + } + + [Fact] + public void NeverSampleSampler_ToString() + { + Assert.Equal("NeverSampleSampler", Samplers.NeverSample.ToString()); + } + + [Fact] + public void ProbabilitySampler_OutOfRangeHighProbability() + { + Assert.Throws(() => Samplers.GetProbabilitySampler(1.01)); + } + + [Fact] + public void ProbabilitySampler_OutOfRangeLowProbability() + { + Assert.Throws(() => Samplers.GetProbabilitySampler(-0.00001)); + } + + + [Fact] + public void ProbabilitySampler_DifferentProbabilities_NotSampledParent() + { + ISampler neverSample = Samplers.GetProbabilitySampler(0.0); + AssertSamplerSamplesWithProbability( + neverSample, notSampledSpanContext, new List(), 0.0); + ISampler alwaysSample = Samplers.GetProbabilitySampler(1.0); + AssertSamplerSamplesWithProbability( + alwaysSample, notSampledSpanContext, new List(), 1.0); + ISampler fiftyPercentSample = Samplers.GetProbabilitySampler(0.5); + AssertSamplerSamplesWithProbability( + fiftyPercentSample, notSampledSpanContext, new List(), 0.5); + ISampler twentyPercentSample = Samplers.GetProbabilitySampler(0.2); + AssertSamplerSamplesWithProbability( + twentyPercentSample, notSampledSpanContext, new List(), 0.2); + ISampler twoThirdsSample = Samplers.GetProbabilitySampler(2.0 / 3.0); + AssertSamplerSamplesWithProbability( + twoThirdsSample, notSampledSpanContext, new List(), 2.0 / 3.0); + } + + [Fact] + public void ProbabilitySampler_DifferentProbabilities_SampledParent() + { + ISampler neverSample = Samplers.GetProbabilitySampler(0.0); + AssertSamplerSamplesWithProbability( + neverSample, sampledSpanContext, new List(), 1.0); + ISampler alwaysSample = Samplers.GetProbabilitySampler(1.0); + AssertSamplerSamplesWithProbability( + alwaysSample, sampledSpanContext, new List(), 1.0); + ISampler fiftyPercentSample = Samplers.GetProbabilitySampler(0.5); + AssertSamplerSamplesWithProbability( + fiftyPercentSample, sampledSpanContext, new List(), 1.0); + ISampler twentyPercentSample = Samplers.GetProbabilitySampler(0.2); + AssertSamplerSamplesWithProbability( + twentyPercentSample, sampledSpanContext, new List(), 1.0); + ISampler twoThirdsSample = Samplers.GetProbabilitySampler(2.0 / 3.0); + AssertSamplerSamplesWithProbability( + twoThirdsSample, sampledSpanContext, new List(), 1.0); + } + + [Fact] + public void ProbabilitySampler_DifferentProbabilities_SampledParentLink() + { + ISampler neverSample = Samplers.GetProbabilitySampler(0.0); + AssertSamplerSamplesWithProbability( + neverSample, notSampledSpanContext, new List() { sampledSpan }, 1.0); + ISampler alwaysSample = Samplers.GetProbabilitySampler(1.0); + AssertSamplerSamplesWithProbability( + alwaysSample, notSampledSpanContext, new List() { sampledSpan }, 1.0); + ISampler fiftyPercentSample = Samplers.GetProbabilitySampler(0.5); + AssertSamplerSamplesWithProbability( + fiftyPercentSample, notSampledSpanContext, new List() { sampledSpan }, 1.0); + ISampler twentyPercentSample = Samplers.GetProbabilitySampler(0.2); + AssertSamplerSamplesWithProbability( + twentyPercentSample, notSampledSpanContext, new List() { sampledSpan }, 1.0); + ISampler twoThirdsSample = Samplers.GetProbabilitySampler(2.0 / 3.0); + AssertSamplerSamplesWithProbability( + twoThirdsSample, notSampledSpanContext, new List() { sampledSpan }, 1.0); + } + + [Fact] + public void ProbabilitySampler_SampleBasedOnTraceId() + { + ISampler defaultProbability = Samplers.GetProbabilitySampler(0.0001); + // This traceId will not be sampled by the ProbabilitySampler because the first 8 bytes as long + // is not less than probability * Long.MAX_VALUE; + ITraceId notSampledtraceId = + TraceId.FromBytes( + new byte[] { + 0x8F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }); + Assert.False( + defaultProbability.ShouldSample( + null, + false, + notSampledtraceId, + SpanId.GenerateRandomId(random), + SPAN_NAME, + new List())); + // This traceId will be sampled by the ProbabilitySampler because the first 8 bytes as long + // is less than probability * Long.MAX_VALUE; + ITraceId sampledtraceId = + TraceId.FromBytes( + new byte[] { + 0x00, + 0x00, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }); + Assert.True( + defaultProbability.ShouldSample( + null, + false, + sampledtraceId, + SpanId.GenerateRandomId(random), + SPAN_NAME, + new List())); + } + + [Fact] + public void ProbabilitySampler_getDescription() + { + Assert.Equal(String.Format("ProbabilitySampler({0:F6})", 0.5), Samplers.GetProbabilitySampler(0.5).Description); + } + + [Fact] + public void ProbabilitySampler_ToString() + { + var result = Samplers.GetProbabilitySampler(0.5).ToString(); + Assert.Contains($"0{CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator}5", result); + } + + // Applies the given sampler to NUM_SAMPLE_TRIES random traceId/spanId pairs. + private static void AssertSamplerSamplesWithProbability( + ISampler sampler, ISpanContext parent, IEnumerable parentLinks, double probability) + { + RandomGenerator random = new RandomGenerator(1234); + int count = 0; // Count of spans with sampling enabled + for (int i = 0; i < NUM_SAMPLE_TRIES; i++) + { + if (sampler.ShouldSample( + parent, + false, + TraceId.GenerateRandomId(random), + SpanId.GenerateRandomId(random), + SPAN_NAME, + parentLinks)) + { + count++; + } + } + double proportionSampled = (double)count / NUM_SAMPLE_TRIES; + // Allow for a large amount of slop (+/- 10%) in number of sampled traces, to avoid flakiness. + Assert.True(proportionSampled < probability + 0.1 && proportionSampled > probability - 0.1); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/SpanBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/SpanBaseTest.cs new file mode 100644 index 000000000..6928288b2 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/SpanBaseTest.cs @@ -0,0 +1,107 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System; + using System.Collections.Generic; + using Moq; + using OpenCensus.Trace.Internal; + using Xunit; + + public class SpanBaseTest + { + private RandomGenerator random; + private ISpanContext spanContext; + private ISpanContext notSampledSpanContext; + private SpanOptions spanOptions; + + public SpanBaseTest() + { + random = new RandomGenerator(1234); + spanContext = + SpanContext.Create( + TraceId.GenerateRandomId(random), + SpanId.GenerateRandomId(random), + TraceOptions.Builder().SetIsSampled(true).Build(), Tracestate.Empty); + notSampledSpanContext = + SpanContext.Create( + TraceId.GenerateRandomId(random), + SpanId.GenerateRandomId(random), + TraceOptions.Default, Tracestate.Empty); + spanOptions = SpanOptions.RecordEvents; + } + + [Fact] + public void NewSpan_WithNullContext() + { + Assert.Throws(() => new NoopSpan(null, default(SpanOptions))); + } + + + [Fact] + public void GetOptions_WhenNullOptions() + { + ISpan span = new NoopSpan(notSampledSpanContext, default(SpanOptions)); + Assert.Equal(SpanOptions.None, span.Options); + } + + [Fact] + public void GetContextAndOptions() + { + ISpan span = new NoopSpan(spanContext, spanOptions); + Assert.Equal(spanContext, span.Context); + Assert.Equal(spanOptions, span.Options); + } + + [Fact] + public void PutAttributeCallsAddAttributesByDefault() + { + var mockSpan = new Mock(spanContext, spanOptions) { CallBase = true }; + NoopSpan span = mockSpan.Object; + IAttributeValue val = AttributeValue.Create(true); + span.PutAttribute("MyKey", val); + span.End(); + mockSpan.Verify((s) => s.PutAttributes(It.Is>((d) => d.ContainsKey("MyKey")))); + + } + + [Fact] + public void EndCallsEndWithDefaultOptions() + { + var mockSpan = new Mock(spanContext, spanOptions) { CallBase = true }; + var span = mockSpan.Object; + span.End(); + mockSpan.Verify((s) => s.End(EndSpanOptions.Default)); + } + + [Fact] + public void AddMessageEventDefaultImplementation() + { + Mock mockSpan = new Mock(); + var span = mockSpan.Object; + + IMessageEvent messageEvent = + MessageEvent.Builder(MessageEventType.Sent, 123) + .SetUncompressedMessageSize(456) + .SetCompressedMessageSize(789) + .Build(); + + span.AddMessageEvent(messageEvent); + mockSpan.Verify((s) => s.AddMessageEvent(messageEvent)); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/SpanBuilderBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/SpanBuilderBaseTest.cs new file mode 100644 index 000000000..ae1348718 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/SpanBuilderBaseTest.cs @@ -0,0 +1,71 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Moq; + using Internal; + using OpenCensus.Common; + using Xunit; + + public class SpanBuilderBaseTest + { + private ITracer tracer; + private Mock spanBuilder = new Mock(SpanKind.Unspecified); + private Mock span = new Mock(); + + public SpanBuilderBaseTest() + { + tracer = Tracing.Tracer; + spanBuilder.Setup((b) => b.StartSpan()).Returns(span.Object); + } + + [Fact] + public void StartScopedSpan() + { + Assert.Same(BlankSpan.Instance, tracer.CurrentSpan); + IScope scope = spanBuilder.Object.StartScopedSpan(); + try + { + Assert.Same(span.Object, tracer.CurrentSpan); + } + finally + { + scope.Dispose(); + } + span.Verify(s => s.End(EndSpanOptions.Default)); + Assert.Same(BlankSpan.Instance, tracer.CurrentSpan); + } + + [Fact] + public void StartScopedSpan_WithParam() + { + Assert.Same(BlankSpan.Instance, tracer.CurrentSpan); + + IScope scope = spanBuilder.Object.StartScopedSpan(out ISpan outSpan); + try + { + Assert.Same(outSpan, tracer.CurrentSpan); + } + finally + { + scope.Dispose(); + } + span.Verify(s => s.End(EndSpanOptions.Default)); + Assert.Same(BlankSpan.Instance, tracer.CurrentSpan); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/SpanBuilderTest.cs b/test/OpenCensus.Tests/Impl/Trace/SpanBuilderTest.cs new file mode 100644 index 000000000..ba26c598a --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/SpanBuilderTest.cs @@ -0,0 +1,356 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System; + using System.Collections.Generic; + using Moq; + using OpenCensus.Common; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Sampler; + using Xunit; + + public class SpanBuilderTest + { + private static readonly String SPAN_NAME = "MySpanName"; + private SpanBuilderOptions spanBuilderOptions; + private TraceParams alwaysSampleTraceParams = TraceParams.Default.ToBuilder().SetSampler(Samplers.AlwaysSample).Build(); + private readonly IRandomGenerator randomHandler = new FakeRandomHandler(); + private IStartEndHandler startEndHandler = Mock.Of(); + private ITraceConfig traceConfig = Mock.Of(); + + public SpanBuilderTest() + { + // MockitoAnnotations.initMocks(this); + spanBuilderOptions = + new SpanBuilderOptions(randomHandler, startEndHandler, traceConfig); + var configMock = Mock.Get(traceConfig); + configMock.Setup((c) => c.ActiveTraceParams).Returns(alwaysSampleTraceParams); + // when(traceConfig.getActiveTraceParams()).thenReturn(alwaysSampleTraceParams); + } + + [Fact] + public void StartSpanNullParent() + { + ISpan span = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions).StartSpan(); + Assert.True(span.Context.IsValid); + Assert.True(span.Options.HasFlag(SpanOptions.RecordEvents)); + Assert.True(span.Context.TraceOptions.IsSampled); + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Null(spanData.ParentSpanId); + Assert.False(spanData.HasRemoteParent); + Assert.InRange(spanData.StartTimestamp, Timestamp.FromDateTimeOffset(DateTimeOffset.Now).AddDuration(Duration.Create(-1, 0)), Timestamp.FromDateTimeOffset(DateTimeOffset.Now).AddDuration(Duration.Create(1, 0))); + Assert.Equal(SPAN_NAME, spanData.Name); + } + + [Fact] + public void StartSpanNullParentWithRecordEvents() + { + ISpan span = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .SetRecordEvents(true) + .StartSpan(); + Assert.True(span.Context.IsValid); + Assert.True(span.Options.HasFlag(SpanOptions.RecordEvents)); + Assert.False(span.Context.TraceOptions.IsSampled); + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Null(spanData.ParentSpanId); + Assert.False(spanData.HasRemoteParent); + } + + [Fact] + public void StartSpanNullParentNoRecordOptions() + { + ISpan span = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .StartSpan(); + Assert.True(span.Context.IsValid); + Assert.False(span.Options.HasFlag(SpanOptions.RecordEvents)); + Assert.False(span.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartChildSpan() + { + ISpan rootSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions).StartSpan(); + Assert.True(rootSpan.Context.IsValid); + Assert.True(rootSpan.Options.HasFlag(SpanOptions.RecordEvents)); + Assert.True(rootSpan.Context.TraceOptions.IsSampled); + Assert.False(((Span)rootSpan).ToSpanData().HasRemoteParent); + ISpan childSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, rootSpan, spanBuilderOptions).StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); + Assert.Equal(rootSpan.Context.SpanId, ((Span)childSpan).ToSpanData().ParentSpanId); + Assert.False(((Span)childSpan).ToSpanData().HasRemoteParent); + Assert.Equal(((Span)rootSpan).TimestampConverter, ((Span)childSpan).TimestampConverter); + } + + [Fact] + public void StartRemoteSpan_NullParent() + { + ISpan span = + SpanBuilder.CreateWithRemoteParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions).StartSpan(); + Assert.True(span.Context.IsValid); + Assert.True(span.Options.HasFlag(SpanOptions.RecordEvents)); + Assert.True(span.Context.TraceOptions.IsSampled); + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Null(spanData.ParentSpanId); + Assert.False(spanData.HasRemoteParent); + } + + [Fact] + public void StartRemoteSpanInvalidParent() + { + ISpan span = + SpanBuilder.CreateWithRemoteParent(SPAN_NAME, SpanKind.Unspecified, SpanContext.Invalid, spanBuilderOptions) + .StartSpan(); + Assert.True(span.Context.IsValid); + Assert.True(span.Options.HasFlag(SpanOptions.RecordEvents)); + Assert.True(span.Context.TraceOptions.IsSampled); + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Null(spanData.ParentSpanId); + Assert.False(spanData.HasRemoteParent); + } + + [Fact] + public void StartRemoteSpan() + { + ISpanContext spanContext = + SpanContext.Create( + TraceId.GenerateRandomId(randomHandler), + SpanId.GenerateRandomId(randomHandler), + TraceOptions.Default, Tracestate.Empty); + ISpan span = + SpanBuilder.CreateWithRemoteParent(SPAN_NAME, SpanKind.Unspecified, spanContext, spanBuilderOptions) + .StartSpan(); + Assert.True(span.Context.IsValid); + Assert.Equal(spanContext.TraceId, span.Context.TraceId); + Assert.True(span.Context.TraceOptions.IsSampled); + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(spanContext.SpanId, spanData.ParentSpanId); + Assert.True(spanData.HasRemoteParent); + } + + [Fact] + public void StartRootSpan_WithSpecifiedSampler() + { + // Apply given sampler before default sampler for root spans. + ISpan rootSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .StartSpan(); + Assert.True(rootSpan.Context.IsValid); + Assert.False(rootSpan.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartRootSpan_WithoutSpecifiedSampler() + { + // Apply default sampler (always true in the tests) for root spans. + ISpan rootSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions).StartSpan(); + Assert.True(rootSpan.Context.IsValid); + Assert.True(rootSpan.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartRemoteChildSpan_WithSpecifiedSampler() + { + ISpan rootSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.AlwaysSample) + .StartSpan(); + Assert.True(rootSpan.Context.IsValid); + Assert.True(rootSpan.Context.TraceOptions.IsSampled); + // Apply given sampler before default sampler for spans with remote parent. + ISpan childSpan = + SpanBuilder.CreateWithRemoteParent(SPAN_NAME, SpanKind.Unspecified, rootSpan.Context, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); + Assert.False(childSpan.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartRemoteChildSpan_WithoutSpecifiedSampler() + { + ISpan rootSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .StartSpan(); + Assert.True(rootSpan.Context.IsValid); + Assert.False(rootSpan.Context.TraceOptions.IsSampled); + // Apply default sampler (always true in the tests) for spans with remote parent. + ISpan childSpan = + SpanBuilder.CreateWithRemoteParent(SPAN_NAME, SpanKind.Unspecified, rootSpan.Context, spanBuilderOptions) + .StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); + Assert.True(childSpan.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartChildSpan_WithSpecifiedSampler() + { + ISpan rootSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.AlwaysSample) + .StartSpan(); + Assert.True(rootSpan.Context.IsValid); + Assert.True(rootSpan.Context.TraceOptions.IsSampled); + // Apply the given sampler for child spans. + ISpan childSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, rootSpan, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); + Assert.False(childSpan.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartChildSpan_WithoutSpecifiedSampler() + { + ISpan rootSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .StartSpan(); + Assert.True(rootSpan.Context.IsValid); + Assert.False(rootSpan.Context.TraceOptions.IsSampled); + // Don't apply the default sampler (always true) for child spans. + ISpan childSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, rootSpan, spanBuilderOptions).StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); + Assert.False(childSpan.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartChildSpan_SampledLinkedParent() + { + ISpan rootSpanUnsampled = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.NeverSample) + .StartSpan(); + Assert.False(rootSpanUnsampled.Context.TraceOptions.IsSampled); + ISpan rootSpanSampled = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, null, spanBuilderOptions) + .SetSampler(Samplers.AlwaysSample) + .StartSpan(); + Assert.True(rootSpanSampled.Context.TraceOptions.IsSampled); + // Sampled because the linked parent is sampled. + ISpan childSpan = + SpanBuilder.CreateWithParent(SPAN_NAME, SpanKind.Unspecified, rootSpanUnsampled, spanBuilderOptions) + .SetParentLinks(new List() { rootSpanSampled }) + .StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(rootSpanUnsampled.Context.TraceId, childSpan.Context.TraceId); + Assert.True(childSpan.Context.TraceOptions.IsSampled); + } + + [Fact] + public void StartRemoteChildSpan_WithProbabilitySamplerDefaultSampler() + { + var configMock = Mock.Get(traceConfig); + configMock.Setup((c) => c.ActiveTraceParams).Returns(TraceParams.Default); + // This traceId will not be sampled by the ProbabilitySampler because the first 8 bytes as long + // is not less than probability * Long.MAX_VALUE; + ITraceId traceId = + TraceId.FromBytes( + new byte[] { + 0x8F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }); + + // If parent is sampled then the remote child must be sampled. + ISpan childSpan = + SpanBuilder.CreateWithRemoteParent( + SPAN_NAME, + SpanKind.Unspecified, + SpanContext.Create( + traceId, + SpanId.GenerateRandomId(randomHandler), + TraceOptions.Builder().SetIsSampled(true).Build(), Tracestate.Empty), + spanBuilderOptions) + .StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(traceId, childSpan.Context.TraceId); + Assert.True(childSpan.Context.TraceOptions.IsSampled); + childSpan.End(); + + Assert.Equal(TraceParams.Default, traceConfig.ActiveTraceParams); + + // If parent is not sampled then the remote child must be not sampled. + childSpan = + SpanBuilder.CreateWithRemoteParent( + SPAN_NAME, + SpanKind.Unspecified, + SpanContext.Create( + traceId, + SpanId.GenerateRandomId(randomHandler), + TraceOptions.Default, Tracestate.Empty), + spanBuilderOptions) + .StartSpan(); + Assert.True(childSpan.Context.IsValid); + Assert.Equal(traceId, childSpan.Context.TraceId); + Assert.False(childSpan.Context.TraceOptions.IsSampled); + childSpan.End(); + } + + class FakeRandomHandler : IRandomGenerator + { + private readonly Random random; + + public FakeRandomHandler() + { + this.random = new Random(1234); + } + + public Random current() + { + return random; + } + + public void NextBytes(byte[] bytes) + { + random.NextBytes(bytes); + } + } + } + +} diff --git a/test/OpenCensus.Tests/Impl/Trace/SpanContextTest.cs b/test/OpenCensus.Tests/Impl/Trace/SpanContextTest.cs new file mode 100644 index 000000000..cdfd7b9ab --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/SpanContextTest.cs @@ -0,0 +1,122 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class SpanContextTest + { + private static readonly byte[] firstTraceIdBytes = + new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)'a' }; + + private static readonly byte[] secondTraceIdBytes = + new byte[] { 0, 0, 0, 0, 0, 0, 0, (byte)'0', 0, 0, 0, 0, 0, 0, 0, 0 }; + + private static readonly byte[] firstSpanIdBytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, (byte)'a' }; + private static readonly byte[] secondSpanIdBytes = new byte[] { (byte)'0', 0, 0, 0, 0, 0, 0, 0 }; + private static readonly ISpanContext first = + SpanContext.Create( + TraceId.FromBytes(firstTraceIdBytes), + SpanId.FromBytes(firstSpanIdBytes), + TraceOptions.Default, Tracestate.Empty); + + private static readonly ISpanContext second = + SpanContext.Create( + TraceId.FromBytes(secondTraceIdBytes), + SpanId.FromBytes(secondSpanIdBytes), + TraceOptions.Builder().SetIsSampled(true).Build(), Tracestate.Empty); + + [Fact] + public void InvalidSpanContext() + { + Assert.Equal(TraceId.Invalid, SpanContext.Invalid.TraceId); + Assert.Equal(SpanId.Invalid, SpanContext.Invalid.SpanId); + Assert.Equal(TraceOptions.Default, SpanContext.Invalid.TraceOptions); + } + + [Fact] + public void IsValid() + { + Assert.False(SpanContext.Invalid.IsValid); + Assert.False( + SpanContext.Create( + TraceId.FromBytes(firstTraceIdBytes), SpanId.Invalid, TraceOptions.Default, Tracestate.Empty) + .IsValid); + Assert.False( + SpanContext.Create( + TraceId.Invalid, SpanId.FromBytes(firstSpanIdBytes), TraceOptions.Default, Tracestate.Empty) + .IsValid); + Assert.True(first.IsValid); + Assert.True(second.IsValid); + } + + [Fact] + public void GetTraceId() + { + Assert.Equal(TraceId.FromBytes(firstTraceIdBytes), first.TraceId); + Assert.Equal(TraceId.FromBytes(secondTraceIdBytes), second.TraceId); + } + + [Fact] + public void GetSpanId() + { + Assert.Equal(SpanId.FromBytes(firstSpanIdBytes), first.SpanId); + Assert.Equal(SpanId.FromBytes(secondSpanIdBytes), second.SpanId); + } + + [Fact] + public void GetTraceOptions() + { + Assert.Equal(TraceOptions.Default, first.TraceOptions); + Assert.Equal(TraceOptions.Builder().SetIsSampled(true).Build(), second.TraceOptions); + } + + [Fact] + public void SpanContext_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // tester.addEqualityGroup( + // first, + // SpanContext.create( + // TraceId.FromBytes(firstTraceIdBytes), + // SpanId.FromBytes(firstSpanIdBytes), + // TraceOptions.DEFAULT), + // SpanContext.create( + // TraceId.FromBytes(firstTraceIdBytes), + // SpanId.FromBytes(firstSpanIdBytes), + // TraceOptions.builder().setIsSampled(false).build())); + // tester.addEqualityGroup( + // second, + // SpanContext.create( + // TraceId.FromBytes(secondTraceIdBytes), + // SpanId.FromBytes(secondSpanIdBytes), + // TraceOptions.builder().setIsSampled(true).build())); + // tester.testEquals(); + } + + [Fact] + public void SpanContext_ToString() + { + Assert.Contains(TraceId.FromBytes(firstTraceIdBytes).ToString(), first.ToString()); + Assert.Contains(SpanId.FromBytes(firstSpanIdBytes).ToString(), first.ToString()); + Assert.Contains(TraceOptions.Default.ToString(), first.ToString()); + Assert.Contains(TraceId.FromBytes(secondTraceIdBytes).ToString(), second.ToString()); + Assert.Contains(SpanId.FromBytes(secondSpanIdBytes).ToString(), second.ToString()); + Assert.Contains(TraceOptions.Builder().SetIsSampled(true).Build().ToString(), second.ToString()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/SpanIdTest.cs b/test/OpenCensus.Tests/Impl/Trace/SpanIdTest.cs new file mode 100644 index 000000000..a3a398f59 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/SpanIdTest.cs @@ -0,0 +1,93 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class SpanIdTest + { + private static readonly byte[] firstBytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, (byte)'a' }; + private static readonly byte[] secondBytes = new byte[] { 0xFF, 0, 0, 0, 0, 0, 0, (byte)'A' }; + private static readonly ISpanId first = SpanId.FromBytes(firstBytes); + private static readonly ISpanId second = SpanId.FromBytes(secondBytes); + + [Fact] + public void invalidSpanId() + { + Assert.Equal(new byte[8], SpanId.Invalid.Bytes); + } + + [Fact] + public void IsValid() + { + Assert.False(SpanId.Invalid.IsValid); + Assert.True(first.IsValid); + Assert.True(second.IsValid); + } + + [Fact] + public void FromLowerBase16() + { + Assert.Equal(SpanId.Invalid, SpanId.FromLowerBase16("0000000000000000")); + Assert.Equal(first, SpanId.FromLowerBase16("0000000000000061")); + Assert.Equal(second, SpanId.FromLowerBase16("ff00000000000041")); + } + + [Fact] + public void ToLowerBase16() + { + Assert.Equal("0000000000000000", SpanId.Invalid.ToLowerBase16()); + Assert.Equal("0000000000000061", first.ToLowerBase16()); + Assert.Equal("ff00000000000041", second.ToLowerBase16()); + } + + [Fact] + public void Bytes() + { + Assert.Equal(firstBytes, first.Bytes); + Assert.Equal(secondBytes, second.Bytes); + + } + + [Fact] + public void SpanId_CompareTo() + { + Assert.Equal(1, first.CompareTo(second)); + Assert.Equal(-1, second.CompareTo(first)); + Assert.Equal(0, first.CompareTo(SpanId.FromBytes(firstBytes))); + } + + [Fact] + public void SpanId_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // tester.addEqualityGroup(SpanId.INVALID, SpanId.INVALID); + // tester.addEqualityGroup(first, SpanId.fromBytes(Arrays.copyOf(firstBytes, firstBytes.length))); + // tester.addEqualityGroup( + // second, SpanId.fromBytes(Arrays.copyOf(secondBytes, secondBytes.length))); + // tester.testEquals(); + } + + [Fact] + public void SpanId_ToString() + { + Assert.Contains("0000000000000000", SpanId.Invalid.ToString()); + Assert.Contains("0000000000000061", first.ToString()); + Assert.Contains("ff00000000000041", second.ToString()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/SpanTest.cs b/test/OpenCensus.Tests/Impl/Trace/SpanTest.cs new file mode 100644 index 000000000..e8753f11b --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/SpanTest.cs @@ -0,0 +1,582 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Moq; + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Internal; + using Xunit; + + public class SpanTest + { + private static readonly String SPAN_NAME = "MySpanName"; + private static readonly String ANNOTATION_DESCRIPTION = "MyAnnotation"; + private readonly RandomGenerator random = new RandomGenerator(1234); + private readonly ISpanContext spanContext; + private readonly ISpanId parentSpanId; + private TimeSpan interval = TimeSpan.FromMilliseconds(0); + private readonly DateTimeOffset startTime = DateTimeOffset.Now; + private readonly Timestamp timestamp; + private readonly Timer timestampConverter; + private readonly SpanOptions noRecordSpanOptions = SpanOptions.None; + private readonly SpanOptions recordSpanOptions = SpanOptions.RecordEvents; + private readonly IDictionary attributes = new Dictionary(); + private readonly IDictionary expectedAttributes; + private IStartEndHandler startEndHandler = Mock.Of(); + + public SpanTest() + { + timestamp = Timestamp.FromDateTimeOffset(startTime); + timestampConverter = Timer.StartNew(startTime, () => interval); + spanContext = SpanContext.Create(TraceId.GenerateRandomId(random), SpanId.GenerateRandomId(random), OpenCensus.Trace.TraceOptions.Default, Tracestate.Empty); + parentSpanId = SpanId.GenerateRandomId(random); + attributes.Add( + "MyStringAttributeKey", AttributeValue.StringAttributeValue("MyStringAttributeValue")); + attributes.Add("MyLongAttributeKey", AttributeValue.LongAttributeValue(123L)); + attributes.Add("MyBooleanAttributeKey", AttributeValue.BooleanAttributeValue(false)); + expectedAttributes = new Dictionary(attributes); + expectedAttributes.Add( + "MySingleStringAttributeKey", + AttributeValue.StringAttributeValue("MySingleStringAttributeValue")); + } + + [Fact] + public void ToSpanData_NoRecordEvents() + { + ISpan span = + Span.StartSpan( + spanContext, + noRecordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + // Check that adding trace events after Span#End() does not throw any exception. + span.PutAttributes(attributes); + span.AddAnnotation(Annotation.FromDescription(ANNOTATION_DESCRIPTION)); + span.AddAnnotation(ANNOTATION_DESCRIPTION, attributes); + span.AddMessageEvent( + MessageEvent.Builder(MessageEventType.Received, 1).SetUncompressedMessageSize(3).Build()); + span.AddLink(Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan)); + span.End(); + // exception.expect(IllegalStateException); + Assert.Throws(() => ((Span)span).ToSpanData()); + } + + [Fact] + public void NoEventsRecordedAfterEnd() + { + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + span.End(); + // Check that adding trace events after Span#End() does not throw any exception and are not + // recorded. + span.PutAttributes(attributes); + span.PutAttribute( + "MySingleStringAttributeKey", + AttributeValue.StringAttributeValue("MySingleStringAttributeValue")); + span.AddAnnotation(Annotation.FromDescription(ANNOTATION_DESCRIPTION)); + span.AddAnnotation(ANNOTATION_DESCRIPTION, attributes); + span.AddMessageEvent( + MessageEvent.Builder(MessageEventType.Received, 1).SetUncompressedMessageSize(3).Build()); + span.AddLink(Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan)); + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(timestamp, spanData.StartTimestamp); + Assert.Empty(spanData.Attributes.AttributeMap); + Assert.Empty(spanData.Annotations.Events); + Assert.Empty(spanData.MessageEvents.Events); + Assert.Empty(spanData.Links.Links); + Assert.Equal(Status.Ok, spanData.Status); + Assert.Equal(timestamp, spanData.EndTimestamp); + } + + [Fact] + public void ToSpanData_ActiveSpan() + { + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + true, + TraceParams.Default, + startEndHandler, + timestampConverter); + + span.PutAttribute( + "MySingleStringAttributeKey", + AttributeValue.StringAttributeValue("MySingleStringAttributeValue")); + span.PutAttributes(attributes); + interval = TimeSpan.FromMilliseconds(100); + span.AddAnnotation(Annotation.FromDescription(ANNOTATION_DESCRIPTION)); + interval = TimeSpan.FromMilliseconds(200); + span.AddAnnotation(ANNOTATION_DESCRIPTION, attributes); + interval = TimeSpan.FromMilliseconds(300); + IMessageEvent networkEvent = + MessageEvent.Builder(MessageEventType.Received, 1).SetUncompressedMessageSize(3).Build(); + span.AddMessageEvent(networkEvent); + interval = TimeSpan.FromMilliseconds(400); + ILink link = Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan); + span.AddLink(link); + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(spanContext, spanData.Context); + Assert.Equal(SPAN_NAME, spanData.Name); + Assert.Equal(parentSpanId, spanData.ParentSpanId); + Assert.True(spanData.HasRemoteParent); + Assert.Equal(0, spanData.Attributes.DroppedAttributesCount); + Assert.Equal(expectedAttributes, spanData.Attributes.AttributeMap); + Assert.Equal(0, spanData.Annotations.DroppedEventsCount); + Assert.Equal(2, spanData.Annotations.Events.Count()); + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(100))), spanData.Annotations.Events.ToList()[0].Timestamp); + Assert.Equal(Annotation.FromDescription(ANNOTATION_DESCRIPTION), spanData.Annotations.Events.ToList()[0].Event); + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(200))), spanData.Annotations.Events.ToList()[1].Timestamp); + Assert.Equal(Annotation.FromDescriptionAndAttributes(ANNOTATION_DESCRIPTION, attributes), spanData.Annotations.Events.ToList()[1].Event); + Assert.Equal(0, spanData.MessageEvents.DroppedEventsCount); + Assert.Single(spanData.MessageEvents.Events); + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(300))), spanData.MessageEvents.Events.First().Timestamp); + Assert.Equal(networkEvent, spanData.MessageEvents.Events.First().Event); + Assert.Equal(0, spanData.Links.DroppedLinksCount); + Assert.Single(spanData.Links.Links); + Assert.Equal(link, spanData.Links.Links.First()); + Assert.Equal(timestamp, spanData.StartTimestamp); + Assert.Null(spanData.Status); + Assert.Null(spanData.EndTimestamp); + + var startEndMock = Mock.Get(startEndHandler); + var spanBase = span as SpanBase; + startEndMock.Verify(s => s.OnStart(spanBase), Times.Once); + } + + [Fact] + public void GoSpanData_EndedSpan() + { + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + + span.PutAttribute( + "MySingleStringAttributeKey", + AttributeValue.StringAttributeValue("MySingleStringAttributeValue")); + span.PutAttributes(attributes); + interval = TimeSpan.FromMilliseconds(100); + span.AddAnnotation(Annotation.FromDescription(ANNOTATION_DESCRIPTION)); + interval = TimeSpan.FromMilliseconds(200); + span.AddAnnotation(ANNOTATION_DESCRIPTION, attributes); + interval = TimeSpan.FromMilliseconds(300); + IMessageEvent networkEvent = + MessageEvent.Builder(MessageEventType.Received, 1).SetUncompressedMessageSize(3).Build(); + span.AddMessageEvent(networkEvent); + ILink link = Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan); + span.AddLink(link); + interval = TimeSpan.FromMilliseconds(400); + span.End(EndSpanOptions.Builder().SetStatus(Status.Cancelled).Build()); + + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(spanContext, spanData.Context); + Assert.Equal(SPAN_NAME, spanData.Name); + Assert.Equal(parentSpanId, spanData.ParentSpanId); + Assert.False(spanData.HasRemoteParent); + Assert.Equal(0, spanData.Attributes.DroppedAttributesCount); + Assert.Equal(expectedAttributes, spanData.Attributes.AttributeMap); + Assert.Equal(0, spanData.Annotations.DroppedEventsCount); + Assert.Equal(2, spanData.Annotations.Events.Count()); + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(100))), spanData.Annotations.Events.ToList()[0].Timestamp); + Assert.Equal(Annotation.FromDescription(ANNOTATION_DESCRIPTION), spanData.Annotations.Events.ToList()[0].Event); + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(200))), spanData.Annotations.Events.ToList()[1].Timestamp); + Assert.Equal(Annotation.FromDescriptionAndAttributes(ANNOTATION_DESCRIPTION, attributes), spanData.Annotations.Events.ToList()[1].Event); + Assert.Equal(0, spanData.MessageEvents.DroppedEventsCount); + Assert.Single(spanData.MessageEvents.Events); + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(300))), spanData.MessageEvents.Events.First().Timestamp); + Assert.Equal(networkEvent, spanData.MessageEvents.Events.First().Event); + Assert.Equal(0, spanData.Links.DroppedLinksCount); + Assert.Single(spanData.Links.Links); + Assert.Equal(link, spanData.Links.Links.First()); + Assert.Equal(timestamp, spanData.StartTimestamp); + Assert.Equal(Status.Cancelled, spanData.Status); + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(400))), spanData.EndTimestamp); + + var startEndMock = Mock.Get(startEndHandler); + var spanBase = span as SpanBase; + startEndMock.Verify(s => s.OnStart(spanBase), Times.Once); + startEndMock.Verify(s => s.OnEnd(spanBase), Times.Once); + } + + [Fact] + public void Status_ViaSetStatus() + { + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + interval = TimeSpan.FromMilliseconds(100); + Assert.Equal(Status.Ok, span.Status); + ((Span)span).Status = Status.Cancelled; + Assert.Equal(Status.Cancelled, span.Status); + span.End(); + Assert.Equal(Status.Cancelled, span.Status); + + var startEndMock = Mock.Get(startEndHandler); + var spanBase = span as SpanBase; + startEndMock.Verify(s => s.OnStart(spanBase), Times.Once); + } + + [Fact] + public void status_ViaEndSpanOptions() + { + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + interval = TimeSpan.FromMilliseconds(100); + Assert.Equal(Status.Ok, span.Status); + ((Span)span).Status = Status.Cancelled; + Assert.Equal(Status.Cancelled, span.Status); + span.End(EndSpanOptions.Builder().SetStatus(Status.Aborted).Build()); + Assert.Equal(Status.Aborted, span.Status); + + var startEndMock = Mock.Get(startEndHandler); + var spanBase = span as SpanBase; + startEndMock.Verify(s => s.OnStart(spanBase), Times.Once); + } + + [Fact] + public void DroppingAttributes() + { + int maxNumberOfAttributes = 8; + TraceParams traceParams = + TraceParams.Default.ToBuilder().SetMaxNumberOfAttributes(maxNumberOfAttributes).Build(); + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter); + for (int i = 0; i < 2 * maxNumberOfAttributes; i++) + { + IDictionary attributes = new Dictionary(); + attributes.Add("MyStringAttributeKey" + i, AttributeValue.LongAttributeValue(i)); + span.PutAttributes(attributes); + } + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfAttributes, spanData.Attributes.DroppedAttributesCount); + Assert.Equal(maxNumberOfAttributes, spanData.Attributes.AttributeMap.Count); + for (int i = 0; i < maxNumberOfAttributes; i++) + { + Assert.Equal( + AttributeValue.LongAttributeValue(i + maxNumberOfAttributes), + spanData + .Attributes + .AttributeMap["MyStringAttributeKey" + (i + maxNumberOfAttributes)]); + } + span.End(); + spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfAttributes, spanData.Attributes.DroppedAttributesCount); + Assert.Equal(maxNumberOfAttributes, spanData.Attributes.AttributeMap.Count); + for (int i = 0; i < maxNumberOfAttributes; i++) + { + Assert.Equal( + AttributeValue.LongAttributeValue(i + maxNumberOfAttributes), + spanData + .Attributes + .AttributeMap["MyStringAttributeKey" + (i + maxNumberOfAttributes)]); + } + } + + [Fact] + public void DroppingAndAddingAttributes() + { + int maxNumberOfAttributes = 8; + TraceParams traceParams = + TraceParams.Default.ToBuilder().SetMaxNumberOfAttributes(maxNumberOfAttributes).Build(); + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter); + for (int i = 0; i < 2 * maxNumberOfAttributes; i++) + { + IDictionary attributes = new Dictionary(); + attributes.Add("MyStringAttributeKey" + i, AttributeValue.LongAttributeValue(i)); + span.PutAttributes(attributes); + } + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfAttributes, spanData.Attributes.DroppedAttributesCount); + Assert.Equal(maxNumberOfAttributes, spanData.Attributes.AttributeMap.Count); + for (int i = 0; i < maxNumberOfAttributes; i++) + { + Assert.Equal( + AttributeValue.LongAttributeValue(i + maxNumberOfAttributes), + spanData + .Attributes + .AttributeMap["MyStringAttributeKey" + (i + maxNumberOfAttributes)]); + } + for (int i = 0; i < maxNumberOfAttributes / 2; i++) + { + IDictionary attributes = new Dictionary(); + attributes.Add("MyStringAttributeKey" + i, AttributeValue.LongAttributeValue(i)); + span.PutAttributes(attributes); + } + spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfAttributes * 3 / 2, spanData.Attributes.DroppedAttributesCount); + Assert.Equal(maxNumberOfAttributes, spanData.Attributes.AttributeMap.Count); + // Test that we still have in the attributes map the latest maxNumberOfAttributes / 2 entries. + for (int i = 0; i < maxNumberOfAttributes / 2; i++) + { + Assert.Equal( + AttributeValue.LongAttributeValue(i + maxNumberOfAttributes * 3 / 2), + spanData + .Attributes + .AttributeMap["MyStringAttributeKey" + (i + maxNumberOfAttributes * 3 / 2)]); + } + // Test that we have the newest re-added initial entries. + for (int i = 0; i < maxNumberOfAttributes / 2; i++) + { + Assert.Equal(AttributeValue.LongAttributeValue(i), spanData.Attributes.AttributeMap["MyStringAttributeKey" + i]); + } + } + + [Fact] + public void DroppingAnnotations() + { + int maxNumberOfAnnotations = 8; + TraceParams traceParams = + TraceParams.Default.ToBuilder().SetMaxNumberOfAnnotations(maxNumberOfAnnotations).Build(); + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter); + IAnnotation annotation = Annotation.FromDescription(ANNOTATION_DESCRIPTION); + int i = 0; + for (i = 0; i < 2 * maxNumberOfAnnotations; i++) + { + span.AddAnnotation(annotation); + interval += TimeSpan.FromMilliseconds(100); + } + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfAnnotations, spanData.Annotations.DroppedEventsCount); + Assert.Equal(maxNumberOfAnnotations, spanData.Annotations.Events.Count()); + i = 0; + foreach (var te in spanData.Annotations.Events) + { + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(100 * (maxNumberOfAnnotations + i)))), te.Timestamp); + Assert.Equal(annotation, te.Event); + i++; + } + span.End(); + spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfAnnotations, spanData.Annotations.DroppedEventsCount); + Assert.Equal(maxNumberOfAnnotations, spanData.Annotations.Events.Count()); + i = 0; + foreach (var te in spanData.Annotations.Events) + { + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(100 * (maxNumberOfAnnotations + i)))), te.Timestamp); + Assert.Equal(annotation, te.Event); + i++; + } + } + + [Fact] + public void DroppingNetworkEvents() + { + int maxNumberOfNetworkEvents = 8; + TraceParams traceParams = + TraceParams.Default + .ToBuilder() + .SetMaxNumberOfMessageEvents(maxNumberOfNetworkEvents) + .Build(); + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter); + IMessageEvent networkEvent = + MessageEvent.Builder(MessageEventType.Received, 1).SetUncompressedMessageSize(3).Build(); + for (int i = 0; i < 2 * maxNumberOfNetworkEvents; i++) + { + span.AddMessageEvent(networkEvent); + interval += TimeSpan.FromMilliseconds(100); + } + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfNetworkEvents, spanData.MessageEvents.DroppedEventsCount); + Assert.Equal(maxNumberOfNetworkEvents, spanData.MessageEvents.Events.Count()); + var list = spanData.MessageEvents.Events.ToList(); + for (int i = 0; i < maxNumberOfNetworkEvents; i++) + { + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(100 * (maxNumberOfNetworkEvents + i)))), list[i].Timestamp); + Assert.Equal(networkEvent, list[i].Event); + } + span.End(); + spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfNetworkEvents, spanData.MessageEvents.DroppedEventsCount); + Assert.Equal(maxNumberOfNetworkEvents, spanData.MessageEvents.Events.Count()); + list = spanData.MessageEvents.Events.ToList(); + for (int i = 0; i < maxNumberOfNetworkEvents; i++) + { + Assert.Equal(timestamp.AddDuration(Duration.Create(TimeSpan.FromMilliseconds(100 * (maxNumberOfNetworkEvents + i)))), list[i].Timestamp); + Assert.Equal(networkEvent, list[i].Event); + } + } + + [Fact] + public void DroppingLinks() + { + int maxNumberOfLinks = 8; + TraceParams traceParams = + TraceParams.Default.ToBuilder().SetMaxNumberOfLinks(maxNumberOfLinks).Build(); + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + traceParams, + startEndHandler, + timestampConverter); + ILink link = Link.FromSpanContext(spanContext, LinkType.ChildLinkedSpan); + for (int i = 0; i < 2 * maxNumberOfLinks; i++) + { + span.AddLink(link); + } + ISpanData spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfLinks, spanData.Links.DroppedLinksCount); + Assert.Equal(maxNumberOfLinks, spanData.Links.Links.Count()); + foreach (var actualLink in spanData.Links.Links) + { + Assert.Equal(link, actualLink); + } + span.End(); + spanData = ((Span)span).ToSpanData(); + Assert.Equal(maxNumberOfLinks, spanData.Links.DroppedLinksCount); + Assert.Equal(maxNumberOfLinks, spanData.Links.Links.Count()); + foreach (var actualLink in spanData.Links.Links) + { + Assert.Equal(link, actualLink); + } + } + + [Fact] + public void SampleToLocalSpanStore() + { + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + span.End(EndSpanOptions.Builder().SetSampleToLocalSpanStore(true).Build()); + + Assert.True(((Span)span).IsSampleToLocalSpanStore); + ISpan span2 = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + span2.End(); + + Assert.False(((Span)span2).IsSampleToLocalSpanStore); + + var startEndMock = Mock.Get(startEndHandler); + var spanBase = span as SpanBase; + startEndMock.Verify(s => s.OnEnd(spanBase), Times.Exactly(1)); + var spanBase2 = span2 as SpanBase; + startEndMock.Verify(s => s.OnEnd(spanBase2), Times.Exactly(1)); + } + + [Fact] + public void SampleToLocalSpanStore_RunningSpan() + { + ISpan span = + Span.StartSpan( + spanContext, + recordSpanOptions, + SPAN_NAME, + parentSpanId, + false, + TraceParams.Default, + startEndHandler, + timestampConverter); + + Assert.Throws(() => ((Span)span).IsSampleToLocalSpanStore); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/StatusTest.cs b/test/OpenCensus.Tests/Impl/Trace/StatusTest.cs new file mode 100644 index 000000000..ce7d5b82a --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/StatusTest.cs @@ -0,0 +1,52 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class StatusTest + { + [Fact] + public void Status_Ok() + { + Assert.Equal(CanonicalCode.Ok, Status.Ok.CanonicalCode); + Assert.Null(Status.Ok.Description); + Assert.True(Status.Ok.IsOk); + } + + [Fact] + public void CreateStatus_WithDescription() + { + Status status = Status.Unknown.WithDescription("This is an error."); + Assert.Equal(CanonicalCode.Unknown, status.CanonicalCode); + Assert.Equal("This is an error.", status.Description); + Assert.False(status.IsOk); + } + + [Fact] + public void Status_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // tester.addEqualityGroup(Status.OK, Status.OK.withDescription(null)); + // tester.addEqualityGroup( + // Status.CANCELLED.withDescription("ThisIsAnError"), + // Status.CANCELLED.withDescription("ThisIsAnError")); + // tester.addEqualityGroup(Status.UNKNOWN.withDescription("This is an error.")); + // tester.testEquals(); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/TraceComponentTest.cs b/test/OpenCensus.Tests/Impl/Trace/TraceComponentTest.cs new file mode 100644 index 000000000..723182349 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/TraceComponentTest.cs @@ -0,0 +1,48 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using OpenCensus.Common; + using OpenCensus.Internal; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Internal; + using OpenCensus.Trace.Propagation; + using Xunit; + + public class TraceComponentTest + { + private readonly TraceComponent traceComponent = new TraceComponent(new RandomGenerator(), new SimpleEventQueue()); + + [Fact] + public void ImplementationOfTracer() + { + Assert.IsType(traceComponent.Tracer); + } + + [Fact] + public void IplementationOfBinaryPropagationHandler() + { + Assert.IsType(traceComponent.PropagationComponent); + } + + [Fact] + public void ImplementationOfTraceExporter() + { + Assert.IsType(traceComponent.ExportComponent); + } + } +} \ No newline at end of file diff --git a/test/OpenCensus.Tests/Impl/Trace/TraceIdTest.cs b/test/OpenCensus.Tests/Impl/Trace/TraceIdTest.cs new file mode 100644 index 000000000..7359ec8ee --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/TraceIdTest.cs @@ -0,0 +1,96 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class TraceIdTest + { + private static readonly byte[] firstBytes = + new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)'a' }; + + private static readonly byte[] secondBytes = + new byte[] { 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)'A' }; + + private static readonly ITraceId first = TraceId.FromBytes(firstBytes); + private static readonly ITraceId second = TraceId.FromBytes(secondBytes); + + [Fact] + public void invalidTraceId() + { + Assert.Equal(new byte[16], TraceId.Invalid.Bytes); + } + + [Fact] + public void IsValid() + { + Assert.False(TraceId.Invalid.IsValid); + Assert.True(first.IsValid); + Assert.True(second.IsValid); + } + + [Fact] + public void Bytes() + { + Assert.Equal(firstBytes, first.Bytes); + Assert.Equal(secondBytes, second.Bytes); + } + + [Fact] + public void FromLowerBase16() + { + Assert.Equal(TraceId.Invalid, TraceId.FromLowerBase16("00000000000000000000000000000000")); + Assert.Equal(first, TraceId.FromLowerBase16("00000000000000000000000000000061")); + Assert.Equal(second, TraceId.FromLowerBase16("ff000000000000000000000000000041")); + } + + [Fact] + public void ToLowerBase16() + { + Assert.Equal("00000000000000000000000000000000", TraceId.Invalid.ToLowerBase16()); + Assert.Equal("00000000000000000000000000000061", first.ToLowerBase16()); + Assert.Equal("ff000000000000000000000000000041", second.ToLowerBase16()); + } + + [Fact] + public void TraceId_CompareTo() + { + Assert.Equal(1, first.CompareTo(second)); + Assert.Equal(-1, second.CompareTo(first)); + Assert.Equal(0, first.CompareTo(TraceId.FromBytes(firstBytes))); + } + + [Fact] + public void TraceId_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // tester.addEqualityGroup(TraceId.INVALID, TraceId.INVALID); + // tester.addEqualityGroup(first, TraceId.fromBytes(Arrays.copyOf(firstBytes, firstBytes.length))); + // tester.addEqualityGroup( + // second, TraceId.fromBytes(Arrays.copyOf(secondBytes, secondBytes.length))); + // tester.testEquals(); + } + + [Fact] + public void TraceId_ToString() + { + Assert.Contains("00000000000000000000000000000000", TraceId.Invalid.ToString()); + Assert.Contains("00000000000000000000000000000061", first.ToString()); + Assert.Contains("ff000000000000000000000000000041", second.ToString()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/TraceOptionsTest.cs b/test/OpenCensus.Tests/Impl/Trace/TraceOptionsTest.cs new file mode 100644 index 000000000..2494826b1 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/TraceOptionsTest.cs @@ -0,0 +1,82 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Xunit; + + public class TraceOptionsTest + { + private static readonly byte[] firstBytes = { 0xff }; + private static readonly byte[] secondBytes = { 1 }; + private static readonly byte[] thirdBytes = { 6 }; + + [Fact] + public void getOptions() + { + Assert.Equal(0, TraceOptions.Default.Options); + Assert.Equal(0, TraceOptions.Builder().SetIsSampled(false).Build().Options); + Assert.Equal(1, TraceOptions.Builder().SetIsSampled(true).Build().Options); + Assert.Equal(0, TraceOptions.Builder().SetIsSampled(true).SetIsSampled(false).Build().Options); + Assert.Equal(-1, TraceOptions.FromBytes(firstBytes).Options); + Assert.Equal(1, TraceOptions.FromBytes(secondBytes).Options); + Assert.Equal(6, TraceOptions.FromBytes(thirdBytes).Options); + } + + [Fact] + public void IsSampled() + { + Assert.False(TraceOptions.Default.IsSampled); + Assert.True(TraceOptions.Builder().SetIsSampled(true).Build().IsSampled); + } + + [Fact] + public void ToFromBytes() + { + Assert.Equal(firstBytes, TraceOptions.FromBytes(firstBytes).Bytes); + Assert.Equal(secondBytes, TraceOptions.FromBytes(secondBytes).Bytes); + Assert.Equal(thirdBytes, TraceOptions.FromBytes(thirdBytes).Bytes); + } + + [Fact] + public void Builder_FromOptions() + { + Assert.Equal(6 | 1, + TraceOptions.Builder(TraceOptions.FromBytes(thirdBytes)) + .SetIsSampled(true) + .Build() + .Options); + } + + [Fact] + public void traceOptions_EqualsAndHashCode() + { + // EqualsTester tester = new EqualsTester(); + // tester.addEqualityGroup(TraceOptions.DEFAULT); + // tester.addEqualityGroup( + // TraceOptions.FromBytes(secondBytes), TraceOptions.Builder().SetIsSampled(true).build()); + // tester.addEqualityGroup(TraceOptions.FromBytes(firstBytes)); + // tester.testEquals(); + } + + [Fact] + public void traceOptions_ToString() + { + Assert.Contains("sampled=False", TraceOptions.Default.ToString()); + Assert.Contains("sampled=True", TraceOptions.Builder().SetIsSampled(true).Build().ToString()); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/TracerBaseTest.cs b/test/OpenCensus.Tests/Impl/Trace/TracerBaseTest.cs new file mode 100644 index 000000000..e1d0596cf --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/TracerBaseTest.cs @@ -0,0 +1,183 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using System; + using Internal; + using Moq; + using OpenCensus.Common; + using Xunit; + + public class TracerBaseTest + { + private static readonly ITracer noopTracer = TracerBase.NoopTracer; + private static readonly string SPAN_NAME = "MySpanName"; + private TracerBase tracer = Mock.Of(); + private SpanBuilderBase spanBuilder = new Mock(SpanKind.Unspecified).Object; + private SpanBase span = Mock.Of(); + + public TracerBaseTest() + { + } + + [Fact] + public void DefaultGetCurrentSpan() + { + Assert.Equal(BlankSpan.Instance, noopTracer.CurrentSpan); + } + + [Fact] + public void WithSpan_NullSpan() + { + Assert.Throws(() => noopTracer.WithSpan(null)); + } + + [Fact] + public void GetCurrentSpan_WithSpan() + { + Assert.Same(BlankSpan.Instance, noopTracer.CurrentSpan); + IScope ws = noopTracer.WithSpan(span); + try + { + Assert.Same(span, noopTracer.CurrentSpan); + } + finally + { + ws.Dispose(); + } + Assert.Same(BlankSpan.Instance, noopTracer.CurrentSpan); + } + + // [Fact] + // public void wrapRunnable() + // { + // Runnable runnable; + // Assert.Equal(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.Instance); + // runnable = + // tracer.withSpan( + // span, + // new Runnable() { + // @Override + // public void run() + // { + // Assert.Equal(noopTracer.getCurrentSpan()).isSameAs(span); + // } + // }); + // // When we run the runnable we will have the span in the current Context. + // runnable.run(); + // verifyZeroInteractions(span); + // Assert.Equal(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.Instance); + // } + + // [Fact] + // public void wrapCallable() throws Exception + // { + // readonly Object ret = new Object(); + // Callable callable; + // Assert.Equal(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.Instance); + // callable = + // tracer.withSpan( + // span, + // new Callable() { + // @Override + // public Object call() throws Exception + // { + // Assert.Equal(noopTracer.getCurrentSpan()).isSameAs(span); + // return ret; + // } + // }); + // // When we call the callable we will have the span in the current Context. + // Assert.Equal(callable.call()).isEqualTo(ret); + // verifyZeroInteractions(span); + // Assert.Equal(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.Instance); + // } + + [Fact] + public void SpanBuilderWithName_NullName() + { + Assert.Throws(() => noopTracer.SpanBuilder(null)); + } + + [Fact] + public void DefaultSpanBuilderWithName() + { + Assert.Same(BlankSpan.Instance, noopTracer.SpanBuilder(SPAN_NAME).StartSpan()); + } + + [Fact] + public void SpanBuilderWithParentAndName_NullName() + { + Assert.Throws(() => noopTracer.SpanBuilderWithExplicitParent(spanName: null, parent: null)); + } + + [Fact] + public void DefaultSpanBuilderWithParentAndName() + { + Assert.Same(BlankSpan.Instance, noopTracer.SpanBuilderWithExplicitParent(SPAN_NAME, parent: null).StartSpan()); + } + + [Fact] + public void spanBuilderWithRemoteParent_NullName() + { + Assert.Throws(() => noopTracer.SpanBuilderWithRemoteParent(null, remoteParentSpanContext: null)); + } + + [Fact] + public void DefaultSpanBuilderWithRemoteParent_NullParent() + { + Assert.Same(BlankSpan.Instance, noopTracer.SpanBuilderWithRemoteParent(SPAN_NAME, remoteParentSpanContext: null).StartSpan()); + } + + [Fact] + public void DefaultSpanBuilderWithRemoteParent() + { + Assert.Same(BlankSpan.Instance, noopTracer.SpanBuilderWithRemoteParent(SPAN_NAME, remoteParentSpanContext: SpanContext.Invalid).StartSpan()); + } + + [Fact] + public void StartSpanWithParentFromContext() + { + IScope ws = tracer.WithSpan(span); + try + { + Assert.Same(span, tracer.CurrentSpan); + Mock.Get(tracer).Setup((tracer) => tracer.SpanBuilderWithExplicitParent(SPAN_NAME, SpanKind.Unspecified, span)).Returns(spanBuilder); + Assert.Same(spanBuilder, tracer.SpanBuilder(SPAN_NAME)); + } + finally + { + ws.Dispose(); + } + } + + [Fact] + public void StartSpanWithInvalidParentFromContext() + { + IScope ws = tracer.WithSpan(BlankSpan.Instance); + try + { + Assert.Same(BlankSpan.Instance, tracer.CurrentSpan); + Mock.Get(tracer).Setup((t) => t.SpanBuilderWithExplicitParent(SPAN_NAME, SpanKind.Unspecified, BlankSpan.Instance)).Returns(spanBuilder); + Assert.Same(spanBuilder, tracer.SpanBuilder(SPAN_NAME)); + } + finally + { + ws.Dispose(); + } + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/TracerTest.cs b/test/OpenCensus.Tests/Impl/Trace/TracerTest.cs new file mode 100644 index 000000000..51850c2ef --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/TracerTest.cs @@ -0,0 +1,53 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using Moq; + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Internal; + using Xunit; + + public class TracerTest + { + private const string SPAN_NAME = "MySpanName"; + private IStartEndHandler startEndHandler; + private ITraceConfig traceConfig; + private Tracer tracer; + + + public TracerTest() + { + startEndHandler = Mock.Of(); + traceConfig = Mock.Of(); + tracer = new Tracer(new RandomGenerator(), startEndHandler, traceConfig); + } + + [Fact] + public void CreateSpanBuilder() + { + ISpanBuilder spanBuilder = tracer.SpanBuilderWithExplicitParent(SPAN_NAME, parent: BlankSpan.Instance); + Assert.IsType(spanBuilder); + } + + [Fact] + public void CreateSpanBuilderWithRemoteParet() + { + ISpanBuilder spanBuilder = tracer.SpanBuilderWithRemoteParent(SPAN_NAME, remoteParentSpanContext: SpanContext.Invalid); + Assert.IsType(spanBuilder); + } + } +} diff --git a/test/OpenCensus.Tests/Impl/Trace/TracingTest.cs b/test/OpenCensus.Tests/Impl/Trace/TracingTest.cs new file mode 100644 index 000000000..631fdd0d6 --- /dev/null +++ b/test/OpenCensus.Tests/Impl/Trace/TracingTest.cs @@ -0,0 +1,108 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace OpenCensus.Trace.Test +{ + using OpenCensus.Trace.Config; + using OpenCensus.Trace.Export; + using OpenCensus.Trace.Propagation; + using Xunit; + + public class TracingTest + { + // @Rule public ExpectedException thrown = ExpectedException.none(); + + // [Fact] + // public void loadTraceComponent_UsesProvidedClassLoader() + // { + // final RuntimeException toThrow = new RuntimeException("UseClassLoader"); + // thrown.expect(RuntimeException.class); + // thrown.expectMessage("UseClassLoader"); + // Tracing.loadTraceComponent( + // new ClassLoader() + // { + // @Override + // public Class loadClass(String name) + // { + // throw toThrow; + // } + // }); + // } + + // [Fact] + // public void loadTraceComponent_IgnoresMissingClasses() + // { + // ClassLoader classLoader = + // new ClassLoader() { + // @Override + // public Class loadClass(String name) throws ClassNotFoundException { + // throw new ClassNotFoundException(); + // } + // }; + // assertThat(Tracing.loadTraceComponent(classLoader).getClass().getName()) + // .isEqualTo("io.opencensus.trace.TraceComponent$NoopTraceComponent"); + // } + + [Fact(Skip = "need to fix the way tracer being instantiated")] + public void DefaultTracer() + { + Assert.Same(Tracer.NoopTracer, Tracing.Tracer); + } + + [Fact(Skip = "need to fix the way tracer being instantiated")] + public void DefaultBinaryPropagationHandler() + { + Assert.Same(PropagationComponentBase.NoopPropagationComponent, Tracing.PropagationComponent); + } + + [Fact(Skip = "need to fix the way tracer being instantiated")] + public void DefaultTraceExporter() + { + Assert.Equal(ExportComponentBase.NewNoopExportComponent.GetType(), Tracing.ExportComponent.GetType()); + } + + [Fact(Skip = "need to fix the way tracer being instantiated")] + public void DefaultTraceConfig() + { + Assert.Same(TraceConfigBase.NoopTraceConfig, Tracing.TraceConfig); + } + + // [Fact] + // public void ImplementationOfTracer() + // { + // Assert.Type(Tracer()).isInstanceOf(TracerImpl.; + // } + + // [Fact] + // public void ImplementationOfBinaryPropagationHandler() + // { + // assertThat(Tracing.getPropagationComponent()).isInstanceOf(PropagationComponent); + // } + + // [Fact] + // public void ImplementationOfClock() + // { + // assertThat(Tracing.getClock()).isInstanceOf(MillisClock); + // } + + // [Fact] + // public void ImplementationOfTraceExporter() + // { + // assertThat(Tracing.getExportComponent()).isInstanceOf(ExportComponentImpl; + // } + + } +} diff --git a/test/OpenCensus.Tests/OpenCensus.Tests.csproj b/test/OpenCensus.Tests/OpenCensus.Tests.csproj new file mode 100644 index 000000000..a7e7dadc3 --- /dev/null +++ b/test/OpenCensus.Tests/OpenCensus.Tests.csproj @@ -0,0 +1,50 @@ + + + + + Unit test project for OpenCensus + net46;netcoreapp2.0 + netcoreapp2.0 + OpenCensus.Tests + OpenCensus.Tests + OpenCensus;Tracing;Management;Monitoring + http://opencensus.io + Apache-2.0 + OpenCensus + + + + + PreserveNewest + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + All + + + + + + + + + + full + true + + + \ No newline at end of file diff --git a/test/OpenCensus.Tests/xunit.runner.json b/test/OpenCensus.Tests/xunit.runner.json new file mode 100644 index 000000000..9fbc90115 --- /dev/null +++ b/test/OpenCensus.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "maxParallelThreads": 1, + "parallelizeTestCollections": false +} \ No newline at end of file diff --git a/test/TestApp.AspNetCore.2.0/CallbackMiddleware.cs b/test/TestApp.AspNetCore.2.0/CallbackMiddleware.cs new file mode 100644 index 000000000..4eb43f3f7 --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/CallbackMiddleware.cs @@ -0,0 +1,50 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace TestApp.AspNetCore._2._0 +{ + using Microsoft.AspNetCore.Http; + using System.Threading.Tasks; + + public class CallbackMiddleware + { + public class CallbackMiddlewareImpl + { + public virtual async Task ProcessAsync(HttpContext context) + { + return await Task.FromResult(true); + } + } + + private readonly CallbackMiddlewareImpl _impl; + + private readonly RequestDelegate _next; + + public CallbackMiddleware(RequestDelegate next, CallbackMiddlewareImpl impl) + { + _next = next; + _impl = impl; + } + + public async Task InvokeAsync(HttpContext context) + { + if (_impl== null || await _impl.ProcessAsync(context)) + { + await _next(context); + } + } + } +} diff --git a/test/TestApp.AspNetCore.2.0/Controllers/ForwardController.cs b/test/TestApp.AspNetCore.2.0/Controllers/ForwardController.cs new file mode 100644 index 000000000..d260e4156 --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/Controllers/ForwardController.cs @@ -0,0 +1,83 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace TestApp.AspNetCore._2._0.Controllers +{ + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Newtonsoft.Json; + + [Route("api/[controller]")] + public class ForwardController : Controller + { + private readonly HttpClient httpClient; + public ForwardController(HttpClient httpclient) + { + this.httpClient = httpclient; + } + + private async Task CallNextAsync(string url, Data[] arguments) + { + if (url != null) + { + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url) + { + Content = new StringContent(JsonConvert.SerializeObject(arguments), Encoding.UTF8, "application/json"), + }; + var response = await httpClient.SendAsync(request); + return await response.Content.ReadAsStringAsync(); + } + + return "all done"; + } + + // POST api/values + [HttpPost] + public async Task Post([FromBody]Data[] data) + { + var result = string.Empty; + + if (data != null) + { + foreach (var argument in data) + { + if (argument.sleep != null) + { + result = "slept for " + argument.sleep.Value + " ms"; + await Task.Delay(argument.sleep.Value); + } + + result += await CallNextAsync(argument.url, argument.arguments); + } + } + else + { + result = "done"; + } + + return result; + } + } + + public class Data + { + public int? sleep { get; set; } + public string url { get; set; } + public Data[] arguments { get; set; } + } +} \ No newline at end of file diff --git a/test/TestApp.AspNetCore.2.0/Controllers/ValuesController.cs b/test/TestApp.AspNetCore.2.0/Controllers/ValuesController.cs new file mode 100644 index 000000000..091a8f6f9 --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/Controllers/ValuesController.cs @@ -0,0 +1,57 @@ +// +// Copyright 2018, OpenCensus 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. +// + +namespace TestApp.AspNetCore._2._0.Controllers +{ + using System.Collections.Generic; + using Microsoft.AspNetCore.Mvc; + + [Route("api/[controller]")] + public class ValuesController : Controller + { + // GET api/values + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public string Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody]string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/test/TestApp.AspNetCore.2.0/Program.cs b/test/TestApp.AspNetCore.2.0/Program.cs new file mode 100644 index 000000000..b1048075e --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/Program.cs @@ -0,0 +1,33 @@ +// +// Copyright 2018, OpenCensus 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 Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace TestApp.AspNetCore._2._0 +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/test/TestApp.AspNetCore.2.0/Startup.cs b/test/TestApp.AspNetCore.2.0/Startup.cs new file mode 100644 index 000000000..5914e895e --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/Startup.cs @@ -0,0 +1,86 @@ +// +// Copyright 2018, OpenCensus 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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OpenCensus.Collector.AspNetCore; +using OpenCensus.Collector.Dependencies; +using OpenCensus.Trace; +using OpenCensus.Trace.Propagation; +using OpenCensus.Trace.Sampler; +using System.Net.Http; +using OpenCensus.Exporter.Ocagent; +using OpenCensus.Trace.Export; + +namespace TestApp.AspNetCore._2._0 +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + services.AddSingleton(); + + services.AddSingleton(Tracing.Tracer); + services.AddSingleton(Samplers.AlwaysSample); + services.AddSingleton(new RequestsCollectorOptions()); + services.AddSingleton(); + services.AddSingleton(new DependenciesCollectorOptions()); + services.AddSingleton(); + services.AddSingleton(new DefaultPropagationComponent()); + services.AddSingleton(Tracing.ExportComponent); + services.AddSingleton(new CallbackMiddleware.CallbackMiddlewareImpl()); + services.AddSingleton((p) => + { + var exportComponent = p.GetService(); + return new OcagentExporter( + exportComponent, + "localhost:55678", + Environment.MachineName, + "test-app"); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, OcagentExporter agentExporter, IApplicationLifetime applicationLifetime) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseMiddleware(); + app.UseMvc(); + var collector = app.ApplicationServices.GetService(); + var depCollector = app.ApplicationServices.GetService(); + + agentExporter.Start(); + + applicationLifetime.ApplicationStopping.Register(agentExporter.Stop); + } + } +} diff --git a/test/TestApp.AspNetCore.2.0/TestApp.AspNetCore.2.0.csproj b/test/TestApp.AspNetCore.2.0/TestApp.AspNetCore.2.0.csproj new file mode 100644 index 000000000..b348bac1a --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/TestApp.AspNetCore.2.0.csproj @@ -0,0 +1,34 @@ + + + + + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + + + + + + All + + + + diff --git a/test/TestApp.AspNetCore.2.0/appsettings.Development.json b/test/TestApp.AspNetCore.2.0/appsettings.Development.json new file mode 100644 index 000000000..fa8ce71a9 --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/test/TestApp.AspNetCore.2.0/appsettings.json b/test/TestApp.AspNetCore.2.0/appsettings.json new file mode 100644 index 000000000..26bb0ac7a --- /dev/null +++ b/test/TestApp.AspNetCore.2.0/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +}