Repository: openzipkin/zipkin Branch: master Commit: 831e2f354a7a Files: 814 Total size: 3.6 MB Directory structure: gitextract_cfx8oy44/ ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.md │ │ ├── config.yml │ │ └── feature.md │ └── workflows/ │ ├── create_release.yml │ ├── deploy.yml │ ├── docker_push.yml │ ├── lint.yml │ ├── security.yml │ ├── test.yml │ └── test_readme.yml ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .settings.xml ├── LICENSE ├── README.md ├── RELEASE.md ├── SECURITY.md ├── benchmarks/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── test/ │ ├── assembly/ │ │ └── test-jar.xml │ ├── java/ │ │ └── zipkin2/ │ │ ├── EndpointBenchmarks.java │ │ ├── SpanBenchmarks.java │ │ ├── codec/ │ │ │ ├── CodecBenchmarks.java │ │ │ ├── JacksonSpanDecoder.java │ │ │ ├── JacksonSpanDecoderTest.java │ │ │ ├── JsonCodecBenchmarks.java │ │ │ ├── MoshiSpanDecoder.java │ │ │ ├── MoshiSpanDecoderTest.java │ │ │ ├── ProtoCodecBenchmarks.java │ │ │ ├── ProtobufSpanDecoder.java │ │ │ └── WireSpanDecoder.java │ │ ├── collector/ │ │ │ └── MetricsBenchmarks.java │ │ ├── elasticsearch/ │ │ │ └── internal/ │ │ │ └── BulkRequestBenchmarks.java │ │ ├── internal/ │ │ │ ├── DelayLimiterBenchmarks.java │ │ │ ├── Proto3CodecInteropTest.java │ │ │ ├── ReadBufferBenchmarks.java │ │ │ └── WriteBufferBenchmarks.java │ │ └── server/ │ │ ├── ServerIntegratedBenchmark.java │ │ └── internal/ │ │ └── throttle/ │ │ └── ThrottledCallBenchmarks.java │ └── resources/ │ ├── create-datasource-and-dashboard.sh │ ├── prometheus.yml │ ├── simplelogger.properties │ ├── zipkin2-chinese.json │ └── zipkin2-client.json ├── build-bin/ │ ├── README.md │ ├── configure_deploy │ ├── configure_lint │ ├── configure_test │ ├── deploy │ ├── docker/ │ │ ├── configure_docker │ │ ├── configure_docker_push │ │ ├── docker-healthcheck │ │ ├── docker_arch │ │ ├── docker_args │ │ ├── docker_block_on_health │ │ ├── docker_build │ │ ├── docker_push │ │ └── docker_test_image │ ├── docker-compose-zipkin-eureka.yml │ ├── docker-compose-zipkin-ui.yml │ ├── docker-compose-zipkin-uiproxy.yml │ ├── docker-compose-zipkin.yml │ ├── docker_push │ ├── git/ │ │ ├── login_git │ │ └── version_from_trigger_tag │ ├── gpg/ │ │ └── configure_gpg │ ├── javadoc_to_gh_pages │ ├── lint │ ├── maven/ │ │ ├── maven_build │ │ ├── maven_build_or_unjar │ │ ├── maven_deploy │ │ ├── maven_go_offline │ │ ├── maven_opts │ │ ├── maven_release │ │ └── maven_unjar │ ├── maven_go_offline │ ├── maybe_install_npm │ ├── mlc_config.json │ └── test ├── docker/ │ ├── Dockerfile │ ├── RATIONALE.md │ ├── README.md │ ├── examples/ │ │ ├── .dockerignore │ │ ├── README.md │ │ ├── docker-compose-activemq.yml │ │ ├── docker-compose-cassandra.yml │ │ ├── docker-compose-dependencies.yml │ │ ├── docker-compose-elasticsearch.yml │ │ ├── docker-compose-eureka.yml │ │ ├── docker-compose-example.yml │ │ ├── docker-compose-kafka.yml │ │ ├── docker-compose-mysql.yml │ │ ├── docker-compose-prometheus.yml │ │ ├── docker-compose-pulsar.yml │ │ ├── docker-compose-rabbitmq.yml │ │ ├── docker-compose-ui.yml │ │ ├── docker-compose-uiproxy.yml │ │ ├── docker-compose.yml │ │ └── prometheus/ │ │ ├── create-datasource-and-dashboard.sh │ │ └── prometheus.yml │ ├── start-zipkin │ └── test-images/ │ ├── zipkin-activemq/ │ │ ├── Dockerfile │ │ ├── README.md │ │ └── start-activemq │ ├── zipkin-cassandra/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── install.sh │ │ └── start-cassandra │ ├── zipkin-elasticsearch7/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── config/ │ │ │ ├── elasticsearch.yml │ │ │ └── log4j2.properties │ │ └── start-elasticsearch │ ├── zipkin-elasticsearch8/ │ │ ├── Dockerfile │ │ ├── README.md │ │ └── start-elasticsearch │ ├── zipkin-eureka/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── pom.xml │ │ ├── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── zipkin/ │ │ │ │ └── test/ │ │ │ │ ├── EurekaProperties.java │ │ │ │ ├── EurekaSecurity.java │ │ │ │ └── EurekaServer.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ └── start-eureka │ ├── zipkin-kafka/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── install.sh │ │ └── start-kafka-zookeeper │ ├── zipkin-mysql/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── install.sh │ │ └── start-mysql │ ├── zipkin-opensearch2/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── config/ │ │ │ ├── log4j2.properties │ │ │ └── opensearch.yml │ │ └── start-opensearch │ ├── zipkin-pulsar/ │ │ ├── Dockerfile │ │ └── README.md │ ├── zipkin-rabbitmq/ │ │ ├── Dockerfile │ │ ├── README.md │ │ └── config/ │ │ ├── defs.json │ │ └── rabbitmq.conf │ ├── zipkin-ui/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── nginx.conf │ │ └── start-nginx │ └── zipkin-uiproxy/ │ ├── Dockerfile │ ├── README.md │ ├── nginx.conf │ └── start-nginx ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src/ │ └── etc/ │ └── header.txt ├── zipkin/ │ ├── RATIONALE.md │ ├── bnd.bnd │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── zipkin2/ │ │ ├── Annotation.java │ │ ├── Call.java │ │ ├── Callback.java │ │ ├── CheckResult.java │ │ ├── Component.java │ │ ├── DependencyLink.java │ │ ├── Endpoint.java │ │ ├── Span.java │ │ ├── SpanBytesDecoderDetector.java │ │ ├── codec/ │ │ │ ├── BytesDecoder.java │ │ │ ├── BytesEncoder.java │ │ │ ├── DependencyLinkBytesDecoder.java │ │ │ ├── DependencyLinkBytesEncoder.java │ │ │ ├── Encoding.java │ │ │ ├── SpanBytesDecoder.java │ │ │ └── SpanBytesEncoder.java │ │ ├── internal/ │ │ │ ├── AggregateCall.java │ │ │ ├── ClosedComponentException.java │ │ │ ├── DateUtil.java │ │ │ ├── DelayLimiter.java │ │ │ ├── Dependencies.java │ │ │ ├── DependencyLinker.java │ │ │ ├── FilterTraces.java │ │ │ ├── HexCodec.java │ │ │ ├── JsonCodec.java │ │ │ ├── JsonEscaper.java │ │ │ ├── Nullable.java │ │ │ ├── Proto3Codec.java │ │ │ ├── Proto3Fields.java │ │ │ ├── Proto3SpanWriter.java │ │ │ ├── Proto3ZipkinFields.java │ │ │ ├── ReadBuffer.java │ │ │ ├── RecyclableBuffers.java │ │ │ ├── SpanNode.java │ │ │ ├── ThriftCodec.java │ │ │ ├── ThriftEndpointCodec.java │ │ │ ├── ThriftField.java │ │ │ ├── Trace.java │ │ │ ├── TracesAdapter.java │ │ │ ├── V1JsonSpanReader.java │ │ │ ├── V1JsonSpanWriter.java │ │ │ ├── V1SpanWriter.java │ │ │ ├── V1ThriftSpanReader.java │ │ │ ├── V1ThriftSpanWriter.java │ │ │ ├── V2SpanReader.java │ │ │ ├── V2SpanWriter.java │ │ │ └── WriteBuffer.java │ │ ├── storage/ │ │ │ ├── AutocompleteTags.java │ │ │ ├── ForwardingStorageComponent.java │ │ │ ├── GroupByTraceId.java │ │ │ ├── InMemoryStorage.java │ │ │ ├── QueryRequest.java │ │ │ ├── ServiceAndSpanNames.java │ │ │ ├── SpanConsumer.java │ │ │ ├── SpanStore.java │ │ │ ├── StorageComponent.java │ │ │ ├── StrictTraceId.java │ │ │ └── Traces.java │ │ └── v1/ │ │ ├── V1Annotation.java │ │ ├── V1BinaryAnnotation.java │ │ ├── V1Span.java │ │ ├── V1SpanConverter.java │ │ └── V2SpanConverter.java │ └── test/ │ ├── java/ │ │ └── zipkin2/ │ │ ├── AnnotationTest.java │ │ ├── CallTest.java │ │ ├── EndpointTest.java │ │ ├── SpanBytesDecoderDetectorTest.java │ │ ├── SpanTest.java │ │ ├── TestObjects.java │ │ ├── codec/ │ │ │ ├── EncodingTest.java │ │ │ ├── KryoTest.java │ │ │ ├── SpanBytesDecoderTest.java │ │ │ ├── SpanBytesEncoderTest.java │ │ │ └── V1SpanBytesDecoderTest.java │ │ ├── internal/ │ │ │ ├── AggregateCallTest.java │ │ │ ├── DateUtilTest.java │ │ │ ├── DelayLimiterTest.java │ │ │ ├── DependenciesTest.java │ │ │ ├── DependencyLinkerTest.java │ │ │ ├── FilterTracesTest.java │ │ │ ├── HexCodecTest.java │ │ │ ├── JsonCodecTest.java │ │ │ ├── JsonEscaperTest.java │ │ │ ├── Proto3FieldsTest.java │ │ │ ├── Proto3SpanWriterTest.java │ │ │ ├── Proto3ZipkinFieldsTest.java │ │ │ ├── ReadBufferTest.java │ │ │ ├── SpanNodeTest.java │ │ │ ├── TraceTest.java │ │ │ ├── TracesAdapterTest.java │ │ │ ├── V1JsonSpanWriterTest.java │ │ │ ├── V1ThriftSpanWriterTest.java │ │ │ ├── V2SpanWriterTest.java │ │ │ └── WriteBufferTest.java │ │ ├── storage/ │ │ │ ├── ForwardingStorageComponentTest.java │ │ │ ├── GroupByTraceIdTest.java │ │ │ ├── InMemoryStorageTest.java │ │ │ ├── QueryRequestTest.java │ │ │ └── StrictTraceIdTest.java │ │ └── v1/ │ │ ├── SpanConverterTest.java │ │ ├── V1SpanConverterTest.java │ │ └── V1SpanTest.java │ └── resources/ │ └── log4j2.properties ├── zipkin-collector/ │ ├── README.md │ ├── activemq/ │ │ ├── RATIONALE.md │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── activemq/ │ │ │ ├── ActiveMQCollector.java │ │ │ ├── ActiveMQSpanConsumer.java │ │ │ └── LazyInit.java │ │ └── test/ │ │ ├── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── activemq/ │ │ │ ├── ActiveMQExtension.java │ │ │ └── ITActiveMQCollector.java │ │ └── resources/ │ │ └── simplelogger.properties │ ├── core/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ ├── Collector.java │ │ │ ├── CollectorComponent.java │ │ │ ├── CollectorMetrics.java │ │ │ ├── CollectorSampler.java │ │ │ └── InMemoryCollectorMetrics.java │ │ └── test/ │ │ └── java/ │ │ └── zipkin2/ │ │ └── collector/ │ │ ├── CollectorSamplerTest.java │ │ └── CollectorTest.java │ ├── kafka/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── kafka/ │ │ │ ├── KafkaCollector.java │ │ │ └── KafkaCollectorWorker.java │ │ └── test/ │ │ ├── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── kafka/ │ │ │ ├── ITKafkaCollector.java │ │ │ └── KafkaExtension.java │ │ └── resources/ │ │ └── simplelogger.properties │ ├── pom.xml │ ├── pulsar/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── pulsar/ │ │ │ ├── LazyPulsarInit.java │ │ │ ├── PulsarCollector.java │ │ │ └── PulsarSpanConsumer.java │ │ └── test/ │ │ ├── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── pulsar/ │ │ │ ├── ITPulsarCollector.java │ │ │ └── PulsarExtension.java │ │ └── resources/ │ │ └── simplelogger.properties │ ├── rabbitmq/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── rabbitmq/ │ │ │ └── RabbitMQCollector.java │ │ └── test/ │ │ ├── java/ │ │ │ └── zipkin2/ │ │ │ └── collector/ │ │ │ └── rabbitmq/ │ │ │ ├── ITRabbitMQCollector.java │ │ │ ├── RabbitMQCollectorTest.java │ │ │ └── RabbitMQExtension.java │ │ └── resources/ │ │ └── simplelogger.properties │ └── scribe/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── zipkin2/ │ │ └── collector/ │ │ └── scribe/ │ │ ├── NettyScribeServer.java │ │ ├── ScribeCollector.java │ │ ├── ScribeInboundHandler.java │ │ ├── ScribeSpanConsumer.java │ │ └── generated/ │ │ ├── LogEntry.java │ │ ├── ResultCode.java │ │ └── Scribe.java │ └── test/ │ ├── java/ │ │ └── zipkin2/ │ │ └── collector/ │ │ └── scribe/ │ │ ├── ITScribeCollector.java │ │ ├── ScribeCollectorTest.java │ │ └── ScribeSpanConsumerTest.java │ └── resources/ │ └── simplelogger.properties ├── zipkin-junit5/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── zipkin2/ │ │ └── junit5/ │ │ ├── HttpFailure.java │ │ ├── ZipkinDispatcher.java │ │ └── ZipkinExtension.java │ └── test/ │ ├── java/ │ │ └── zipkin2/ │ │ └── junit5/ │ │ └── ZipkinExtensionTest.java │ └── resources/ │ └── simplelogger.properties ├── zipkin-lens/ │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .linguirc │ ├── .npmrc │ ├── .prettierrc.js │ ├── README.md │ ├── index.html │ ├── javadoc/ │ │ └── README.md │ ├── package.json │ ├── pom.xml │ ├── src/ │ │ ├── components/ │ │ │ ├── App/ │ │ │ │ ├── AlertSnackbar.tsx │ │ │ │ ├── App.tsx │ │ │ │ ├── HeaderMenuItem.tsx │ │ │ │ ├── LanguageSelector.test.tsx │ │ │ │ ├── LanguageSelector.tsx │ │ │ │ ├── Layout.test.jsx │ │ │ │ ├── Layout.tsx │ │ │ │ ├── ThemeSelector.tsx │ │ │ │ ├── TraceIdSearch.tsx │ │ │ │ ├── TraceJsonUploader.tsx │ │ │ │ ├── index.js │ │ │ │ ├── slice.test.ts │ │ │ │ └── slice.ts │ │ │ ├── DependenciesPage/ │ │ │ │ ├── DependenciesGraph.test.jsx │ │ │ │ ├── DependenciesGraph.tsx │ │ │ │ ├── DependenciesPage.test.jsx │ │ │ │ ├── DependenciesPage.tsx │ │ │ │ ├── NodeDetailData.test.jsx │ │ │ │ ├── NodeDetailData.tsx │ │ │ │ ├── VizceralWrapper.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── DiscoverPage/ │ │ │ │ ├── Criterion.ts │ │ │ │ ├── DiscoverPage.tsx │ │ │ │ ├── DiscoverPageContent.test.jsx │ │ │ │ ├── DiscoverPageContent.tsx │ │ │ │ ├── LookbackMenu.test.jsx │ │ │ │ ├── LookbackMenu.tsx │ │ │ │ ├── SearchBar/ │ │ │ │ │ ├── CriterionBox.test.jsx │ │ │ │ │ ├── CriterionBox.tsx │ │ │ │ │ ├── HowToUse.tsx │ │ │ │ │ ├── SearchBar.test.jsx │ │ │ │ │ ├── SearchBar.tsx │ │ │ │ │ ├── SuggestionList.tsx │ │ │ │ │ ├── constants.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── TraceSummaryRow.test.jsx │ │ │ │ ├── TraceSummaryRow.tsx │ │ │ │ ├── TraceSummaryTable.tsx │ │ │ │ ├── index.js │ │ │ │ └── lookback.ts │ │ │ ├── TracePage/ │ │ │ │ ├── AnnotationTable/ │ │ │ │ │ ├── AnnotationTable.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── AnnotationTooltip/ │ │ │ │ │ ├── AnnotationTooltip.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Header/ │ │ │ │ │ ├── Header.tsx │ │ │ │ │ ├── HeaderMenu.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── MiniTimeline/ │ │ │ │ │ ├── MiniTimeline.tsx │ │ │ │ │ ├── MiniTimelineOverlay.tsx │ │ │ │ │ ├── MiniTimelineRow.tsx │ │ │ │ │ ├── TimeRangeSelector.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── SpanDetailDrawer/ │ │ │ │ │ ├── AnnotationViewer.tsx │ │ │ │ │ ├── SpanDetailDrawer.tsx │ │ │ │ │ ├── TagList.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── SpanTable/ │ │ │ │ │ ├── SpanTable.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── TickMarkers/ │ │ │ │ │ ├── TickMarkers.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Timeline/ │ │ │ │ │ ├── Timeline.tsx │ │ │ │ │ ├── TimelineHeader.tsx │ │ │ │ │ ├── TimelineRow.tsx │ │ │ │ │ ├── TimelineRowAnnotation.tsx │ │ │ │ │ ├── TimelineRowBar.tsx │ │ │ │ │ ├── TimelineRowEdges.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── TracePage.jsx │ │ │ │ ├── TracePageContent.tsx │ │ │ │ ├── helpers.test.jsx │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.jsx │ │ │ │ └── types.ts │ │ │ ├── UiConfig/ │ │ │ │ ├── UiConfig.jsx │ │ │ │ ├── UiConfig.test.jsx │ │ │ │ ├── constants.js │ │ │ │ └── index.js │ │ │ └── common/ │ │ │ ├── ExplainBox.tsx │ │ │ ├── LoadingIndicator.tsx │ │ │ ├── ServiceBadge.jsx │ │ │ └── ServiceBadge.test.jsx │ │ ├── constants/ │ │ │ ├── api.test.tsx │ │ │ ├── api.ts │ │ │ └── color.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── models/ │ │ │ ├── AdjustedTrace.ts │ │ │ ├── Annotation.ts │ │ │ ├── Dependencies.ts │ │ │ ├── Endpoint.ts │ │ │ ├── Span.ts │ │ │ └── TraceSummary.ts │ │ ├── prop-types/ │ │ │ └── index.js │ │ ├── reducers/ │ │ │ └── index.ts │ │ ├── setupTests.ts │ │ ├── slices/ │ │ │ ├── autocompleteKeysSlice.ts │ │ │ ├── autocompleteValuesSlice.ts │ │ │ ├── dependenciesSlice.ts │ │ │ ├── remoteServicesSlice.ts │ │ │ ├── servicesSlice.ts │ │ │ ├── spansSlice.ts │ │ │ ├── tracesSlice.test.ts │ │ │ └── tracesSlice.ts │ │ ├── store/ │ │ │ ├── configure-store.js │ │ │ └── index.ts │ │ ├── test/ │ │ │ ├── data/ │ │ │ │ ├── malformed.js │ │ │ │ └── skew.js │ │ │ └── util/ │ │ │ └── render-with-default-settings.tsx │ │ ├── translations/ │ │ │ ├── en/ │ │ │ │ ├── messages.d.ts │ │ │ │ └── translations.json │ │ │ ├── es/ │ │ │ │ ├── messages.d.ts │ │ │ │ └── translations.json │ │ │ ├── fr/ │ │ │ │ ├── messages.d.ts │ │ │ │ └── translations.json │ │ │ ├── i18n.ts │ │ │ └── zh-cn/ │ │ │ ├── messages.d.ts │ │ │ └── translations.json │ │ ├── types/ │ │ │ ├── redux-thunk.d.ts │ │ │ ├── styled-components.d.ts │ │ │ └── vizceral-react.d.ts │ │ ├── util/ │ │ │ ├── fetch-resource.js │ │ │ ├── fetch-resource.test.js │ │ │ ├── theme.ts │ │ │ ├── timestamp.js │ │ │ ├── trace.js │ │ │ └── trace.test.js │ │ ├── vite-env.d.ts │ │ └── zipkin/ │ │ ├── clock-skew.js │ │ ├── clock-skew.test.js │ │ ├── dependency-linker.js │ │ ├── dependency-linker.test.js │ │ ├── index.js │ │ ├── span-cleaner.js │ │ ├── span-cleaner.test.js │ │ ├── span-node.js │ │ ├── span-node.test.js │ │ ├── span-row.js │ │ ├── span-row.test.js │ │ ├── trace-constants.js │ │ ├── trace.js │ │ └── trace.test.js │ ├── testdata/ │ │ ├── README.md │ │ ├── ascend.json │ │ ├── envoy.json │ │ ├── messaging-kafka.json │ │ ├── messaging.json │ │ ├── messaging2.json │ │ ├── simple-db-p6.json │ │ ├── skew.json │ │ ├── smartthings-mobile-web-install.json │ │ ├── smartthings-oauth-authorization.json │ │ └── yelp.json │ ├── tsconfig.json │ └── vite.config.ts ├── zipkin-server/ │ ├── RATIONALE.md │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── zipkin/ │ │ │ │ └── server/ │ │ │ │ └── ZipkinServer.java │ │ │ └── zipkin2/ │ │ │ └── server/ │ │ │ └── internal/ │ │ │ ├── BodyIsExceptionMessage.java │ │ │ ├── ConditionalOnSelfTracing.java │ │ │ ├── ConditionalOnThrottledStorage.java │ │ │ ├── EnableZipkinServer.java │ │ │ ├── InternalZipkinConfiguration.java │ │ │ ├── JsonUtil.java │ │ │ ├── MicrometerCollectorMetrics.java │ │ │ ├── WrappingExecutorService.java │ │ │ ├── ZipkinActuatorImporter.java │ │ │ ├── ZipkinConfiguration.java │ │ │ ├── ZipkinGrpcCollector.java │ │ │ ├── ZipkinHttpCollector.java │ │ │ ├── ZipkinHttpConfiguration.java │ │ │ ├── ZipkinModuleImporter.java │ │ │ ├── ZipkinQueryApiV2.java │ │ │ ├── activemq/ │ │ │ │ ├── ZipkinActiveMQCollectorConfiguration.java │ │ │ │ └── ZipkinActiveMQCollectorProperties.java │ │ │ ├── banner/ │ │ │ │ └── ZipkinBanner.java │ │ │ ├── brave/ │ │ │ │ ├── SelfTracingProperties.java │ │ │ │ ├── TracedCall.java │ │ │ │ ├── TracingStorageComponent.java │ │ │ │ └── ZipkinSelfTracingConfiguration.java │ │ │ ├── cassandra3/ │ │ │ │ ├── ZipkinCassandra3StorageConfiguration.java │ │ │ │ └── ZipkinCassandra3StorageProperties.java │ │ │ ├── elasticsearch/ │ │ │ │ ├── BasicAuthInterceptor.java │ │ │ │ ├── BasicCredentials.java │ │ │ │ ├── DynamicCredentialsFileLoader.java │ │ │ │ ├── HttpClientFactory.java │ │ │ │ ├── InitialEndpointSupplier.java │ │ │ │ ├── LazyHttpClientImpl.java │ │ │ │ ├── SslUtil.java │ │ │ │ ├── ZipkinElasticsearchStorageConfiguration.java │ │ │ │ └── ZipkinElasticsearchStorageProperties.java │ │ │ ├── eureka/ │ │ │ │ ├── ZipkinEurekaDiscoveryConfiguration.java │ │ │ │ └── ZipkinEurekaDiscoveryProperties.java │ │ │ ├── health/ │ │ │ │ ├── ComponentHealth.java │ │ │ │ └── ZipkinHealthController.java │ │ │ ├── kafka/ │ │ │ │ ├── ZipkinKafkaCollectorConfiguration.java │ │ │ │ └── ZipkinKafkaCollectorProperties.java │ │ │ ├── mysql/ │ │ │ │ ├── ZipkinMySQLStorageConfiguration.java │ │ │ │ ├── ZipkinMySQLStorageProperties.java │ │ │ │ └── ZipkinSelfTracingMySQLStorageConfiguration.java │ │ │ ├── package-info.java │ │ │ ├── prometheus/ │ │ │ │ ├── ZipkinMetricsController.java │ │ │ │ └── ZipkinPrometheusMetricsConfiguration.java │ │ │ ├── pulsar/ │ │ │ │ ├── ZipkinPulsarCollectorConfiguration.java │ │ │ │ └── ZipkinPulsarCollectorProperties.java │ │ │ ├── rabbitmq/ │ │ │ │ ├── ZipkinRabbitMQCollectorConfiguration.java │ │ │ │ └── ZipkinRabbitMQCollectorProperties.java │ │ │ ├── scribe/ │ │ │ │ └── ZipkinScribeCollectorConfiguration.java │ │ │ ├── throttle/ │ │ │ │ ├── LimiterMetrics.java │ │ │ │ ├── MicrometerThrottleMetrics.java │ │ │ │ ├── ThrottledCall.java │ │ │ │ ├── ThrottledStorageComponent.java │ │ │ │ └── ZipkinStorageThrottleProperties.java │ │ │ └── ui/ │ │ │ ├── CompressionProperties.java │ │ │ ├── ZipkinUiConfiguration.java │ │ │ └── ZipkinUiProperties.java │ │ └── resources/ │ │ ├── info.json │ │ ├── simplelogger.properties │ │ ├── zipkin-server-shared.yml │ │ ├── zipkin-server.yml │ │ └── zipkin.txt │ └── test/ │ ├── java/ │ │ └── zipkin2/ │ │ ├── collector/ │ │ │ ├── activemq/ │ │ │ │ └── ZipkinActiveMQCollectorPropertiesOverrideTest.java │ │ │ ├── kafka/ │ │ │ │ └── ZipkinKafkaCollectorPropertiesOverrideTest.java │ │ │ ├── pulsar/ │ │ │ │ └── ZipkinPulsarCollectorPropertiesOverrideTest.java │ │ │ ├── rabbitmq/ │ │ │ │ └── ZipkinRabbitMQCollectorPropertiesOverrideTest.java │ │ │ └── scribe/ │ │ │ └── ZipkinScribeCollectorConfigurationTest.java │ │ ├── server/ │ │ │ └── internal/ │ │ │ ├── ITActuatorMappings.java │ │ │ ├── ITZipkinGrpcCollector.java │ │ │ ├── ITZipkinServer.java │ │ │ ├── ITZipkinServerAutocomplete.java │ │ │ ├── ITZipkinServerCORS.java │ │ │ ├── ITZipkinServerHttpCollectorDisabled.java │ │ │ ├── ITZipkinServerQueryDisabled.java │ │ │ ├── ITZipkinServerSsl.java │ │ │ ├── ITZipkinServerTimeout.java │ │ │ ├── InMemoryConfiguration.java │ │ │ ├── NoOpMeterRegistryConfiguration.java │ │ │ ├── ZipkinActuatorImporterTest.java │ │ │ ├── ZipkinHttpConfigurationTest.java │ │ │ ├── ZipkinModuleImporterTest.java │ │ │ ├── activemq/ │ │ │ │ ├── Access.java │ │ │ │ ├── ZipkinActiveMQCollectorConfigurationTest.java │ │ │ │ └── ZipkinActiveMQCollectorPropertiesTest.java │ │ │ ├── banner/ │ │ │ │ └── ZipkinBannerTest.java │ │ │ ├── brave/ │ │ │ │ └── ITZipkinSelfTracing.java │ │ │ ├── cassandra3/ │ │ │ │ └── Access.java │ │ │ ├── elasticsearch/ │ │ │ │ ├── Access.java │ │ │ │ ├── ITElasticsearchAuth.java │ │ │ │ ├── ITElasticsearchClientInitialization.java │ │ │ │ ├── ITElasticsearchDynamicCredentials.java │ │ │ │ ├── ITElasticsearchHealthCheck.java │ │ │ │ ├── ITElasticsearchNoVerify.java │ │ │ │ ├── ITElasticsearchSelfTracing.java │ │ │ │ ├── InitialEndpointSupplierTest.java │ │ │ │ ├── TestResponses.java │ │ │ │ └── ZipkinElasticsearchStorageConfigurationTest.java │ │ │ ├── eureka/ │ │ │ │ ├── BaseITZipkinEureka.java │ │ │ │ ├── ITZipkinEureka.java │ │ │ │ ├── ITZipkinEurekaAuthenticated.java │ │ │ │ ├── ZipkinEurekaDiscoveryConfigurationTest.java │ │ │ │ └── ZipkinEurekaDiscoveryPropertiesTest.java │ │ │ ├── health/ │ │ │ │ ├── ComponentHealthTest.java │ │ │ │ ├── ITZipkinHealth.java │ │ │ │ ├── ITZipkinHealthDown.java │ │ │ │ └── ZipkinHealthControllerTest.java │ │ │ ├── kafka/ │ │ │ │ ├── Access.java │ │ │ │ ├── ZipkinKafkaCollectorConfigurationTest.java │ │ │ │ └── ZipkinKafkaCollectorPropertiesTest.java │ │ │ ├── mysql/ │ │ │ │ └── Access.java │ │ │ ├── prometheus/ │ │ │ │ ├── ITZipkinMetrics.java │ │ │ │ ├── ITZipkinMetricsDirty.java │ │ │ │ └── ZipkinPrometheusMetricsConfigurationTest.java │ │ │ ├── pulsar/ │ │ │ │ ├── Access.java │ │ │ │ ├── ZipkinPulsarCollectorConfigurationTest.java │ │ │ │ └── ZipkinPulsarCollectorPropertiesTest.java │ │ │ ├── rabbitmq/ │ │ │ │ ├── Access.java │ │ │ │ ├── ZipkinRabbitMQCollectorConfigurationTest.java │ │ │ │ └── ZipkinRabbitMQCollectorPropertiesTest.java │ │ │ ├── throttle/ │ │ │ │ ├── FakeCall.java │ │ │ │ ├── ThrottledCallTest.java │ │ │ │ └── ThrottledStorageComponentTest.java │ │ │ └── ui/ │ │ │ ├── ITZipkinUiConfiguration.java │ │ │ └── ZipkinUiConfigurationTest.java │ │ └── storage/ │ │ ├── cassandra/ │ │ │ └── ZipkinCassandraStorageAutoConfigurationTest.java │ │ └── mysql/ │ │ └── v1/ │ │ └── ZipkinMySQLStorageConfigurationTest.java │ └── resources/ │ ├── application.yml │ ├── banner.txt │ ├── es-credentials │ ├── es-credentials-invalid │ ├── keystore.jks │ ├── keystore.p12 │ ├── log4j2.properties │ ├── simplelogger.properties │ └── zipkin-lens/ │ ├── index.html │ └── test.txt ├── zipkin-storage/ │ ├── README.md │ ├── cassandra/ │ │ ├── RATIONALE.md │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── zipkin2/ │ │ │ │ └── storage/ │ │ │ │ └── cassandra/ │ │ │ │ ├── AnnotationCodec.java │ │ │ │ ├── CassandraAutocompleteTags.java │ │ │ │ ├── CassandraSpanConsumer.java │ │ │ │ ├── CassandraSpanStore.java │ │ │ │ ├── CassandraStorage.java │ │ │ │ ├── CassandraStorageBuilder.java │ │ │ │ ├── CassandraUtil.java │ │ │ │ ├── DefaultSessionFactory.java │ │ │ │ ├── EndpointCodec.java │ │ │ │ ├── InsertSpan.java │ │ │ │ ├── InsertTraceByServiceRemoteService.java │ │ │ │ ├── InsertTraceByServiceSpan.java │ │ │ │ ├── LazySession.java │ │ │ │ ├── Schema.java │ │ │ │ ├── SelectAutocompleteValues.java │ │ │ │ ├── SelectDependencies.java │ │ │ │ ├── SelectFromSpan.java │ │ │ │ ├── SelectRemoteServiceNames.java │ │ │ │ ├── SelectServiceNames.java │ │ │ │ ├── SelectSpanNames.java │ │ │ │ ├── SelectTraceIdsFromServiceRemoteService.java │ │ │ │ ├── SelectTraceIdsFromServiceSpan.java │ │ │ │ ├── SelectTraceIdsFromSpan.java │ │ │ │ └── internal/ │ │ │ │ ├── HostAndPort.java │ │ │ │ ├── KeyspaceMetadataUtil.java │ │ │ │ ├── Resources.java │ │ │ │ ├── SessionBuilder.java │ │ │ │ └── call/ │ │ │ │ ├── AccumulateAllResults.java │ │ │ │ ├── AccumulateTraceIdTsUuid.java │ │ │ │ ├── AggregateIntoMap.java │ │ │ │ ├── DeduplicatingInsert.java │ │ │ │ ├── DistinctSortedStrings.java │ │ │ │ ├── InsertEntry.java │ │ │ │ ├── IntersectKeySets.java │ │ │ │ ├── IntersectMaps.java │ │ │ │ └── ResultSetFutureCall.java │ │ │ └── resources/ │ │ │ ├── zipkin2-schema-indexes.cql │ │ │ ├── zipkin2-schema-upgrade-1.cql │ │ │ ├── zipkin2-schema-upgrade-2.cql │ │ │ └── zipkin2-schema.cql │ │ └── test/ │ │ ├── java/ │ │ │ └── zipkin2/ │ │ │ └── storage/ │ │ │ └── cassandra/ │ │ │ ├── CassandraContainer.java │ │ │ ├── CassandraSpanConsumerTest.java │ │ │ ├── CassandraSpanStoreTest.java │ │ │ ├── CassandraStorageBuilderTest.java │ │ │ ├── CassandraStorageTest.java │ │ │ ├── CassandraUtilTest.java │ │ │ ├── ITCassandraStorage.java │ │ │ ├── ITCassandraStorageHeavy.java │ │ │ ├── ITEnsureSchema.java │ │ │ ├── ITSpanConsumer.java │ │ │ ├── InternalForTests.java │ │ │ ├── SchemaTest.java │ │ │ └── internal/ │ │ │ ├── HostAndPortTest.java │ │ │ ├── SessionBuilderTest.java │ │ │ └── call/ │ │ │ ├── DeduplicatingInsertTest.java │ │ │ └── ResultSetFutureCallTest.java │ │ └── resources/ │ │ ├── autocomplete_tags-stress.yaml │ │ ├── remote_service_by_service-stress.yaml │ │ ├── simplelogger.properties │ │ ├── span-stress.yaml │ │ ├── span_by_service-stress.yaml │ │ ├── trace_by_service_remote_service-stress.yaml │ │ ├── trace_by_service_span-stress.yaml │ │ ├── zipkin2-schema-indexes-original.cql │ │ └── zipkin2-test-schema.cql │ ├── elasticsearch/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── zipkin2/ │ │ │ └── elasticsearch/ │ │ │ ├── BaseVersion.java │ │ │ ├── BodyConverters.java │ │ │ ├── ElasticsearchAutocompleteTags.java │ │ │ ├── ElasticsearchSpanConsumer.java │ │ │ ├── ElasticsearchSpanStore.java │ │ │ ├── ElasticsearchSpecificTemplates.java │ │ │ ├── ElasticsearchStorage.java │ │ │ ├── ElasticsearchVersion.java │ │ │ ├── EnsureIndexTemplate.java │ │ │ ├── IndexTemplates.java │ │ │ ├── OpensearchSpecificTemplates.java │ │ │ ├── OpensearchVersion.java │ │ │ ├── VersionSpecificTemplates.java │ │ │ └── internal/ │ │ │ ├── BulkCallBuilder.java │ │ │ ├── BulkIndexWriter.java │ │ │ ├── IndexNameFormatter.java │ │ │ ├── Internal.java │ │ │ ├── JsonReaders.java │ │ │ ├── JsonSerializers.java │ │ │ └── client/ │ │ │ ├── Aggregation.java │ │ │ ├── HttpCall.java │ │ │ ├── SearchCallFactory.java │ │ │ ├── SearchRequest.java │ │ │ └── SearchResultConverter.java │ │ └── test/ │ │ ├── java/ │ │ │ └── zipkin2/ │ │ │ └── elasticsearch/ │ │ │ ├── BaseVersionTest.java │ │ │ ├── ElasticsearchAutocompleteTagsTest.java │ │ │ ├── ElasticsearchSpanConsumerTest.java │ │ │ ├── ElasticsearchSpanStoreTest.java │ │ │ ├── ElasticsearchSpecificTemplatesTest.java │ │ │ ├── ElasticsearchStorageTest.java │ │ │ ├── ElasticsearchVersionTest.java │ │ │ ├── InternalForTests.java │ │ │ ├── JsonReadersTest.java │ │ │ ├── JsonSerializersTest.java │ │ │ ├── OpensearchSpecificTemplatesTest.java │ │ │ ├── OpensearchVersionTest.java │ │ │ ├── SearchResultConverterTest.java │ │ │ ├── TestResponses.java │ │ │ ├── integration/ │ │ │ │ ├── ElasticsearchBaseExtension.java │ │ │ │ ├── ElasticsearchExtension.java │ │ │ │ ├── ITElasticsearchStorage.java │ │ │ │ ├── ITElasticsearchStorageV7.java │ │ │ │ ├── ITElasticsearchStorageV8.java │ │ │ │ ├── ITEnsureIndexTemplate.java │ │ │ │ ├── ITOpenSearchStorageV2.java │ │ │ │ ├── IgnoredDeprecationWarnings.java │ │ │ │ └── OpenSearchExtension.java │ │ │ └── internal/ │ │ │ ├── BulkCallBuilderTest.java │ │ │ ├── BulkIndexWriterTest.java │ │ │ ├── IndexNameFormatterTest.java │ │ │ └── client/ │ │ │ ├── HttpCallTest.java │ │ │ ├── SearchCallFactoryTest.java │ │ │ └── SearchRequestTest.java │ │ └── resources/ │ │ └── simplelogger.properties │ ├── mysql-v1/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── zipkin2/ │ │ │ │ └── storage/ │ │ │ │ └── mysql/ │ │ │ │ └── v1/ │ │ │ │ ├── AggregateDependencies.java │ │ │ │ ├── DSLContexts.java │ │ │ │ ├── DataSourceCall.java │ │ │ │ ├── DependencyLinkV2SpanIterator.java │ │ │ │ ├── HasErrorCount.java │ │ │ │ ├── HasIpv6.java │ │ │ │ ├── HasPreAggregatedDependencies.java │ │ │ │ ├── HasRemoteServiceName.java │ │ │ │ ├── HasTraceIdHigh.java │ │ │ │ ├── MySQLAutocompleteTags.java │ │ │ │ ├── MySQLSpanConsumer.java │ │ │ │ ├── MySQLSpanStore.java │ │ │ │ ├── MySQLStorage.java │ │ │ │ ├── Pair.java │ │ │ │ ├── PeekingIterator.java │ │ │ │ ├── Schema.java │ │ │ │ ├── SelectAnnotationServiceNames.java │ │ │ │ ├── SelectAutocompleteValues.java │ │ │ │ ├── SelectDependencies.java │ │ │ │ ├── SelectRemoteServiceNames.java │ │ │ │ ├── SelectSpanNames.java │ │ │ │ ├── SelectSpansAndAnnotations.java │ │ │ │ └── internal/ │ │ │ │ └── generated/ │ │ │ │ ├── DefaultCatalog.java │ │ │ │ ├── Indexes.java │ │ │ │ ├── Keys.java │ │ │ │ ├── Tables.java │ │ │ │ ├── Zipkin.java │ │ │ │ └── tables/ │ │ │ │ ├── ZipkinAnnotations.java │ │ │ │ ├── ZipkinDependencies.java │ │ │ │ └── ZipkinSpans.java │ │ │ └── resources/ │ │ │ └── mysql.sql │ │ └── test/ │ │ ├── java/ │ │ │ └── zipkin2/ │ │ │ └── storage/ │ │ │ └── mysql/ │ │ │ └── v1/ │ │ │ ├── DependencyLinkV2SpanIteratorTest.java │ │ │ ├── ITMySQLStorage.java │ │ │ ├── MySQLExtension.java │ │ │ ├── MySQLStorageTest.java │ │ │ ├── SchemaTest.java │ │ │ └── SelectSpansAndAnnotationsTest.java │ │ └── resources/ │ │ ├── drop_zipkin_tables.sql │ │ └── simplelogger.properties │ └── pom.xml └── zipkin-tests/ ├── pom.xml └── src/ ├── main/ │ └── java/ │ └── zipkin2/ │ ├── TestObjects.java │ └── storage/ │ ├── ITAutocompleteTags.java │ ├── ITDependencies.java │ ├── ITDependenciesHeavy.java │ ├── ITSearchEnabledFalse.java │ ├── ITServiceAndSpanNames.java │ ├── ITSpanStore.java │ ├── ITSpanStoreHeavy.java │ ├── ITStorage.java │ ├── ITStrictTraceIdFalse.java │ └── ITTraces.java └── test/ └── java/ └── zipkin2/ └── storage/ └── ITInMemoryStorage.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ # https://docs.docker.com/engine/reference/builder/#dockerignore-file ** # Scripts that run within Docker !build-bin/maybe_install_npm !build-bin/maven/maven_build !build-bin/maven/maven_build_or_unjar !build-bin/maven/maven_opts !build-bin/maven/maven_unjar !build-bin/docker/docker-healthcheck !docker/start-zipkin !/zipkin-server/target/zipkin-server-*exec.jar !/zipkin-server/target/zipkin-server-*slim.jar !docker/test-images/zipkin-activemq/start-activemq !docker/test-images/zipkin-cassandra/install.sh !docker/test-images/zipkin-cassandra/start-cassandra !zipkin-storage/cassandra/src/main/resources/*.cql !docker/test-images/zipkin-elasticsearch7/config/ !docker/test-images/zipkin-elasticsearch7/start-elasticsearch !docker/test-images/zipkin-elasticsearch8/config/ !docker/test-images/zipkin-elasticsearch8/start-elasticsearch !docker/test-images/zipkin-opensearch2/config/ !docker/test-images/zipkin-opensearch2/start-opensearch !docker/test-images/zipkin-eureka/src/ !docker/test-images/zipkin-eureka/pom.xml !docker/test-images/zipkin-eureka/start-eureka !docker/test-images/zipkin-kafka/install.sh !docker/test-images/zipkin-kafka/start-kafka-zookeeper !docker/test-images/zipkin-mysql/install.sh !docker/test-images/zipkin-mysql/start-mysql !zipkin-storage/mysql-v1/src/main/resources/mysql.sql !docker/test-images/zipkin-rabbitmq/config/ !docker/test-images/zipkin-ui/nginx.conf !docker/test-images/zipkin-ui/start-nginx !zipkin-lens/target/zipkin-lens-*.jar !docker/test-images/zipkin-uiproxy/nginx.conf !docker/test-images/zipkin-uiproxy/start-nginx # Allow on-demand "mvn package". referenced in pom.xml must be added even if not built !zipkin/src/main/** !zipkin-collector/src/main/** !zipkin-collector/core/src/main/** !zipkin-collector/activemq/src/main/** !zipkin-collector/kafka/src/main/** !zipkin-collector/rabbitmq/src/main/** !zipkin-collector/scribe/src/main/** !zipkin-collector/pulsar/src/main/** !zipkin-junit5/src/main/** !zipkin-storage/src/main/** !zipkin-storage/cassandra/src/main/** !zipkin-storage/mysql-v1/src/main/** !zipkin-storage/elasticsearch/src/main/** !zipkin-server/src/main/** !zipkin-tests/src/main/** !zipkin-lens/javadoc/** !zipkin-lens/public/** !zipkin-lens/src/** !zipkin-lens/.linguirc !zipkin-lens/.npmrc !zipkin-lens/index.html !zipkin-lens/package-lock.json !zipkin-lens/package.json !zipkin-lens/pom.xml !zipkin-lens/tsconfig.json !zipkin-lens/vite.config.ts !**/pom.xml ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .gitattributes ================================================ *.java text=auto eol=lf *.bnd text=auto eol=lf *.sh text=auto eol=lf .*_profile text=auto eol=lf *.md text=auto eol=lf *.txt text=auto eol=lf *.yml text=auto eol=lf *.yaml text=auto eol=lf *.xml text=auto eol=lf *.properties text=auto eol=lf *.js text=auto eol=lf *.jsx text=auto eol=lf *.ts text=auto eol=lf *.tsx text=auto eol=lf *.json text=auto eol=lf *.mustache text=auto eol=lf *.css text=auto eol=lf ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing to Zipkin If you would like to contribute code, fork this GitHub repository and send a pull request (on a branch other than `master` or `gh-pages`). When submitting code, please apply [Square Code Style](https://github.com/square/java-code-styles). * If the settings import correctly, CodeStyle/Java will be named Square and use 2 space tab and indent, with 4 space continuation indent. ## License By contributing your code, you agree to license your contribution under the terms of the [APLv2](../LICENSE). All files are released with the Apache 2.0 license. If you are adding a new file it should have a header like below. This can be automatically added by running `./mvnw com.mycila:license-maven-plugin:format`. ``` /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ ``` ================================================ FILE: .github/ISSUE_TEMPLATE/bug.md ================================================ --- name: Bug about: If you’ve found a bug, spend the time to write a failing test. Bugs with tests get fixed and stay fixed. If you have a solution in mind, skip raising an issue and open a pull request instead. labels: bug --- ## Describe the Bug A clear and concise description of what the bug is. If you have a solution in mind, skip raising an issue and open a pull request instead. If this is a UI issue... * Attach a screen shot or animated gif showing what you think is wrong * Include JSON of a trace that produces it, being careful to not include private data * You can literally include the JSON in [triple-backticks](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax#quoting-code) * Otherwise, you can use a [gist](https://gist.github.com/) or pastebin Regardless, the best is to spend some time to write a failing test. Bugs with tests get fixed and stay fixed. ## Steps to Reproduce Steps to reproduce the behavior: ## Expected Behaviour Suggest what you think correct behaviour should be. Note, it may be solved differently depending on the problem. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Question url: https://gitter.im/openzipkin/zipkin about: Please ask questions about how to do something or to understand why something isn't working on our Gitter chat - we'll be happy to respond there in detail. This issue tracker is not for questions. ================================================ FILE: .github/ISSUE_TEMPLATE/feature.md ================================================ --- name: Feature Request about: Please first, look at existing issues to see if the feature has been requested before. labels: enhancement --- Please first, look at [existing issues](https://github.com/openzipkin/zipkin/issues) to see if the feature has been requested before. If you don't find anything tell us what problem you’re trying to solve. Often a solution already exists! Don’t send pull requests to implement new features without first getting our support. Sometimes we leave features out on purpose to keep the project small. ## Feature Description of the feature ## Rationale Why would this feature help others besides me? ## Example Scenario What kind of use cases would benefit from this feature? ## Prior Art * Links to prior art * More links ================================================ FILE: .github/workflows/create_release.yml ================================================ --- name: create_release # We create a release version on a trigger tag, regardless of if the commit is # documentation-only. on: # yamllint disable-line rule:truthy push: tags: # e.g. release-1.2.3 - 'release-[0-9]+.[0-9]+.[0-9]+**' jobs: create_release: runs-on: ubuntu-24.04 # newest available distribution, aka noble steps: - name: Checkout Repository uses: actions/checkout@v4 with: # Prevent use of implicit GitHub Actions read-only GITHUB_TOKEN # because maven-release-plugin pushes commits to master. token: ${{ secrets.GH_TOKEN }} - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range java-version: '17' # earliest LTS supported by Spring Boot 3 - name: Cache local Maven repository uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-jdk-17-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-jdk-17-maven- - name: Create Release env: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= # * makes release commits and tags # * needs repo:status, public_repo # * referenced in .settings.xml GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | # GITHUB_REF will be refs/tags/release-MAJOR.MINOR.PATCH build-bin/git/login_git && build-bin/maven/maven_release $(echo ${GITHUB_REF} | cut -d/ -f 3) ================================================ FILE: .github/workflows/deploy.yml ================================================ # yamllint --format github .github/workflows/deploy.yml --- name: deploy # We deploy on master and release versions, regardless of if the commit is # documentation-only or not. on: # yamllint disable-line rule:truthy push: branches: - master # Don't deploy tags because the same commit for MAJOR.MINOR.PATCH is also # on master: Redundant deployment of a release version will fail uploading. tags-ignore: - '*' jobs: deploy: runs-on: ubuntu-24.04 # newest available distribution, aka noble steps: - name: Checkout Repository uses: actions/checkout@v4 with: # Prevent use of implicit GitHub Actions read-only GITHUB_TOKEN # because javadoc_to_gh_pages pushes commits to the gh-pages branch. token: ${{ secrets.GH_TOKEN }} # Allow build-bin/javadoc_to_gh_pages to fetch and push gh-pages # See https://github.com/actions/checkout/issues/578 fetch-depth: 0 - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range java-version: '17' # earliest LTS supported by Spring Boot 3 - name: Cache local Maven repository uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-jdk-17-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-jdk-17-maven- - name: Cache NPM Packages uses: actions/cache@v4 with: path: ~/.npm # yamllint disable-line rule:line-length key: ${{ runner.os }}-npm-packages-${{ hashFiles('zipkin-lens/package-lock.json') }} # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Deploy env: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= # * pushes gh-pages during build-bin/javadoc_to_gh_pages # * pushes Docker images to ghcr.io # * create via https://github.com/settings/tokens # * needs repo:status, public_repo, write:packages, delete:packages GH_TOKEN: ${{ secrets.GH_TOKEN }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} # GPG_PASSPHRASE= # * referenced in .settings.xml GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # SONATYPE_USER= # * deploys snapshots and releases to Sonatype # * needs access to io.zipkin via OSSRH-16669 # * generate via https://oss.sonatype.org/#profile;User%20Token # * referenced in .settings.xml SONATYPE_USER: ${{ secrets.SONATYPE_USER }} # SONATYPE_PASSWORD= # * referenced in .settings.xml SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} # DOCKERHUB_USER= # * only push repos in openzipkin org to Docker Hub on release DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} # DOCKERHUB_TOKEN= # * Access Token from here https://hub.docker.com/settings/security DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} run: | # GITHUB_REF = refs/heads/master or refs/tags/MAJOR.MINOR.PATCH build-bin/configure_deploy && build-bin/deploy $(echo ${GITHUB_REF} | cut -d/ -f 3) ================================================ FILE: .github/workflows/docker_push.yml ================================================ --- name: docker_push # We re-push docker on a trigger tag, regardless of if the commit is # documentation-only. on: # yamllint disable-line rule:truthy push: tags: # e.g. docker-1.2.3 - 'docker-[0-9]+.[0-9]+.[0-9]+**' jobs: docker_push: runs-on: ubuntu-24.04 # newest available distribution, aka noble steps: - name: Checkout Repository uses: actions/checkout@v4 # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Docker Push run: | # GITHUB_REF = refs/tags/docker-MAJOR.MINOR.PATCH build-bin/git/login_git && build-bin/docker/configure_docker_push && build-bin/docker_push $(echo ${GITHUB_REF} | cut -d/ -f 3) env: # GH_USER= GH_USER: ${{ secrets.GH_USER }} # GH_TOKEN= # * pushes Docker images to ghcr.io # * create via https://github.com/settings/tokens # * needs repo:status, public_repo, write:packages, delete:packages GH_TOKEN: ${{ secrets.GH_TOKEN }} # DOCKERHUB_USER= # * only push repos in openzipkin org to Docker Hub on release DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} # DOCKERHUB_TOKEN= # * Access Token from here https://hub.docker.com/settings/security DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} ================================================ FILE: .github/workflows/lint.yml ================================================ --- name: lint on: # yamllint disable-line rule:truthy push: # non-tagged pushes to master branches: - master tags-ignore: - '*' paths: - '**/*.md' - '.github/workflows/*.yml' - './build-bin/*lint' - ./build-bin/mlc_config.json pull_request: # pull requests targeted at the master branch. branches: - master paths: - '**/*.md' - '.github/workflows/*.yml' - './build-bin/*lint' - ./build-bin/mlc_config.json jobs: lint: name: lint runs-on: ubuntu-24.04 # newest available distribution, aka noble # skip commits made by the release plugin if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Lint run: | build-bin/configure_lint build-bin/lint ================================================ FILE: .github/workflows/security.yml ================================================ --- name: security # We don't scan documentation-only commits. on: # yamllint disable-line rule:truthy push: # non-tagged pushes to master branches: - master tags-ignore: - '*' paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json pull_request: # pull requests targeted at the master branch. branches: - master paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json jobs: security: name: security runs-on: ubuntu-24.04 # newest available distribution, aka numbat # skip commits made by the release plugin if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" steps: - name: Checkout Repository uses: actions/checkout@v4 - uses: actions/cache@v4 name: Cache Trivy Database with: path: .trivy key: ${{ runner.os }}-trivy restore-keys: ${{ runner.os }}-trivy - name: Run Trivy vulnerability and secret scanner uses: aquasecurity/trivy-action@master id: trivy env: # See https://github.com/aquasecurity/trivy/discussions/7668 TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db with: scan-type: 'fs' scan-ref: '.' # scan the entire repository scanners: vuln,secret exit-code: '1' severity: HIGH,CRITICAL output: trivy-report.md cache-dir: .trivy - name: Set Summary shell: bash if: ${{ failure() && steps.trivy.conclusion == 'failure' }} # Add the Trivy report to the summary # # Note: This will cause a workflow error if trivy-report.md > the step # limit 1MiB. If this was due to too many CVEs, consider fixing them ;) run: cat trivy-report.md >> $GITHUB_STEP_SUMMARY ================================================ FILE: .github/workflows/test.yml ================================================ --- name: test # We don't test documentation-only commits. on: # yamllint disable-line rule:truthy push: # non-tagged pushes to master branches: - master tags-ignore: - '*' paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json pull_request: # pull requests targeted at the master branch. branches: - master paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json jobs: test: name: test (JDK ${{ matrix.java_version }}) runs-on: ubuntu-24.04 # newest available distribution, aka noble # skip commits made by the release plugin if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" strategy: fail-fast: false # don't fail fast as some failures are LTS specific matrix: # match with maven-enforcer-plugin rules in pom.xml include: - java_version: 17 # earliest LTS supported by Spring Boot 3 maven_args: -Prelease -Dgpg.skip - java_version: 21 # Most recent LTS steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range java-version: ${{ matrix.java_version }} - name: Cache local Maven repository uses: actions/cache@v4 with: path: ~/.m2/repository # yamllint disable-line rule:line-length key: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven- - name: Cache NPM Packages uses: actions/cache@v4 with: path: ~/.npm # yamllint disable-line rule:line-length key: ${{ runner.os }}-npm-packages-${{ hashFiles('zipkin-lens/package-lock.json') }} - name: Test without Docker run: | build-bin/maven_go_offline && build-bin/test -DexcludedGroups=docker ${{ matrix.maven_args }} test_docker: runs-on: ubuntu-24.04 # newest available distribution, aka noble # skip commits made by the release plugin if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" strategy: matrix: include: - name: zipkin-collector-activemq - name: zipkin-collector-kafka - name: zipkin-collector-rabbitmq - name: zipkin-collector-pulsar - name: zipkin-storage-cassandra - name: zipkin-storage-elasticsearch - name: zipkin-storage-mysql-v1 - name: zipkin-server steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range java-version: '21' # Most recent LTS - name: Cache local Maven repository uses: actions/cache@v4 with: path: ~/.m2/repository # yamllint disable-line rule:line-length key: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-jdk-${{ matrix.java_version }}-maven- # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Test with Docker # configure_test seeds NPM cache, which isn't needed for these tests. # # What we are doing here is configuring docker, then installing the # module's dependencies prior to testing it with docker. This allows # us to avoid running tests except the leaf module. run: | build-bin/docker/configure_docker && build-bin/maven/maven_go_offline && build-bin/maven/maven_build -pl :${{ matrix.name }} --am && build-bin/test -Dgroups=docker -pl :${{ matrix.name }} env: MAVEN_GOAL: install # docker build needs dependencies in mavenLocal MAVEN_CONFIG: '-Dlicense.skip=true' # license check already run ================================================ FILE: .github/workflows/test_readme.yml ================================================ --- name: test_readme # These test build commands mentioned in various README.md files. # # We don't test documentation-only commits. on: # yamllint disable-line rule:truthy push: # non-tagged pushes to master branches: - master tags-ignore: - '*' paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json pull_request: # pull requests targeted at the master branch. branches: - master paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json jobs: zipkin-server: name: zipkin-server/README.md ${{ matrix.name }} runs-on: ${{ matrix.os }} # skip commits made by the release plugin if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" timeout-minutes: 5 strategy: matrix: include: # Not ubuntu as already tested as a part of the docker job - name: macos os: macos-14 - name: windows os: windows-2022 steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range java-version: '21' # Most recent LTS cache: 'maven' - name: Cache NPM Packages uses: actions/cache@v4 with: path: ~/.npm # yamllint disable-line rule:line-length key: ${{ runner.os }}-npm-packages-${{ hashFiles('zipkin-lens/package-lock.json') }} - name: Execute Server Build # command from zipkin-server/README.md run: ./mvnw --also-make -pl zipkin-server clean package env: MAVEN_CONFIG: '-T1C -q --batch-mode -DskipTests' docker: runs-on: ubuntu-24.04 # newest available distribution, aka noble # skip commits made by the release plugin if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" timeout-minutes: 20 steps: - name: Checkout Repository uses: actions/checkout@v4 # Remove apt repos that are known to break from time to time. # See https://github.com/actions/virtual-environments/issues/323 - name: Remove broken apt repos run: | for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/` do sudo rm $apt_file done - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' # zulu as it supports a wide version range java-version: '21' # Most recent LTS cache: 'maven' # Don't attempt to cache Docker. Sensitive information can be stolen # via forks, and login session ends up in ~/.docker. This is ok because # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. - name: Cache NPM Packages uses: actions/cache@v4 with: path: ~/.npm # yamllint disable-line rule:line-length key: ${{ runner.os }}-npm-packages-${{ hashFiles('zipkin-lens/package-lock.json') }} - name: Build zipkin-server # redundant, but needed for docker/README.md run: ./mvnw --also-make -pl zipkin-server clean package env: MAVEN_CONFIG: '-T1C -q --batch-mode -DskipTests' - name: docker/README.md - openzipkin/zipkin run: | build-bin/docker/docker_build openzipkin/zipkin:test && build-bin/docker/docker_test_image openzipkin/zipkin:test && docker run --rm --entrypoint=/bin/sh openzipkin/zipkin:test \ -c 'cd BOOT-INF/lib && du -sk *aarch* *x86* *a64*' || true env: RELEASE_FROM_MAVEN_BUILD: true - name: docker/README.md - openzipkin/zipkin-slim run: | build-bin/docker/docker_build openzipkin/zipkin-slim:test && build-bin/docker/docker_test_image openzipkin/zipkin-slim:test && docker run --rm --entrypoint=/bin/sh openzipkin/zipkin-slim:test \ -c 'cd BOOT-INF/lib && du -sk *aarch* *x86* *a64*' || true env: DOCKER_TARGET: zipkin-slim RELEASE_FROM_MAVEN_BUILD: true - name: docker/test-images/zipkin-ui/README.md run: | build-bin/docker/docker_build ${DOCKER_TAG} && build-bin/docker/docker_test_image ${DOCKER_TAG} env: DOCKER_TAG: openzipkin/zipkin-ui:test DOCKER_FILE: docker/test-images/zipkin-ui/Dockerfile RELEASE_FROM_MAVEN_BUILD: true - name: docker/test-images/zipkin-uiproxy/README.md run: | build-bin/docker/docker_build ${DOCKER_TAG} && build-bin/docker/docker_test_image ${DOCKER_TAG} env: DOCKER_TAG: openzipkin/zipkin-uiproxy:test DOCKER_FILE: docker/test-images/zipkin-uiproxy/Dockerfile - name: docker/test-images/zipkin-activemq/README.md run: | build-bin/docker/docker_build ${DOCKER_TAG} && build-bin/docker/docker_test_image ${DOCKER_TAG} env: DOCKER_TAG: openzipkin/zipkin-activemq:test DOCKER_FILE: docker/test-images/zipkin-activemq/Dockerfile - name: docker/test-images/zipkin-cassandra/README.md run: | build-bin/docker/docker_build ${DOCKER_TAG} && build-bin/docker/docker_test_image ${DOCKER_TAG} env: DOCKER_TAG: openzipkin/zipkin-cassandra:test DOCKER_FILE: docker/test-images/zipkin-cassandra/Dockerfile - name: docker/test-images/zipkin-elasticsearch7/README.md run: | build-bin/docker/docker_build ${DOCKER_TAG} && build-bin/docker/docker_test_image ${DOCKER_TAG} env: DOCKER_TAG: openzipkin/zipkin-elasticsearch7:test DOCKER_FILE: docker/test-images/zipkin-elasticsearch7/Dockerfile - name: docker/test-images/zipkin-elasticsearch8/README.md run: | build-bin/docker/docker_build ${DOCKER_TAG} && build-bin/docker/docker_test_image ${DOCKER_TAG} env: DOCKER_TAG: openzipkin/zipkin-elasticsearch8:test DOCKER_FILE: docker/test-images/zipkin-elasticsearch8/Dockerfile - name: docker/test-images/zipkin-opensearch2/README.md run: | build-bin/docker/docker_build ${DOCKER_TAG} && build-bin/docker/docker_test_image ${DOCKER_TAG} env: DOCKER_TAG: openzipkin/zipkin-opensearch2:test DOCKER_FILE: docker/test-images/zipkin-opensearch2/Dockerfile - name: docker/test-images/zipkin-eureka/README.md run: | build-bin/docker/docker_build openzipkin/zipkin-eureka:test && build-bin/docker/docker_test_image openzipkin/zipkin-eureka:test env: DOCKER_FILE: docker/test-images/zipkin-eureka/Dockerfile - name: docker/test-images/zipkin-kafka/README.md run: | build-bin/docker/docker_build openzipkin/zipkin-kafka:test && build-bin/docker/docker_test_image openzipkin/zipkin-kafka:test env: DOCKER_FILE: docker/test-images/zipkin-kafka/Dockerfile - name: docker/test-images/zipkin-mysql/README.md run: | build-bin/docker/docker_build openzipkin/zipkin-mysql:test && build-bin/docker/docker_test_image openzipkin/zipkin-mysql:test env: DOCKER_FILE: docker/test-images/zipkin-mysql/Dockerfile - name: docker/test-images/zipkin-rabbitmq/README.md run: | build-bin/docker/docker_build openzipkin/zipkin-rabbitmq:test && build-bin/docker/docker_test_image openzipkin/zipkin-rabbitmq:test env: DOCKER_FILE: docker/test-images/zipkin-rabbitmq/Dockerfile - name: docker/test-images/zipkin-pulsar/README.md run: | build-bin/docker/docker_build openzipkin/zipkin-pulsar:test && build-bin/docker/docker_test_image openzipkin/zipkin-pulsar:test env: DOCKER_FILE: docker/test-images/zipkin-pulsar/Dockerfile ================================================ FILE: .gitignore ================================================ *~ #* *# .#* dependency-reduced-pom.xml .factorypath .classpath .project .settings/ .springBeans target/ _site/ .idea *.iml *.swp # quickstart or temporary copies of zipkin's jar /*.jar # temporary directory used by build-bin/javadoc_to_gh_pages for building gh-pages /javadoc-builddir .DS_Store # This project does not use Gradle but some developers may use it to e.g., setup a Node environment. # It doesn't hurt to just exclude it here. .gradle # This project does not use Yarn but some developers may use it to e.g., start zipkin-lens dev server. # It doesn't hurt to just exclude it here. yarn.lock ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. wrapperVersion=3.3.2 distributionType=bin distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar ================================================ FILE: .settings.xml ================================================ gpg.passphrase ${env.GPG_PASSPHRASE} ossrh ${env.SONATYPE_USER} ${env.SONATYPE_PASSWORD} github.com zipkinci ${env.GH_TOKEN} ================================================ FILE: LICENSE ================================================ 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. This product contains a modified part of Gson, distributed by Google: * License: Apache License v2.0 * Homepage: https://github.com/google/gson This product contains a modified part of Guava, distributed by Google: * License: Apache License v2.0 * Homepage: https://github.com/google/guava This product contains a modified part of Okio, distributed by Square: * License: Apache License v2.0 * Homepage: https://github.com/square/okio ================================================ FILE: README.md ================================================ # zipkin [![Gitter chat](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/openzipkin/zipkin) [![Build Status](https://github.com/openzipkin/zipkin/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/openzipkin/zipkin/actions?query=workflow%3Atest+branch%3Amaster) [![Maven Central](https://img.shields.io/maven-central/v/io.zipkin/zipkin-server.svg)](https://central.sonatype.com/search?q=zipkin&namespace=io.zipkin&name=zipkin-server) [Zipkin](https://zipkin.io) is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in service architectures. Features include both the collection and lookup of this data. If you have a trace ID in a log file, you can jump directly to it. Otherwise, you can query based on attributes such as service, operation name, tags and duration. Some interesting data will be summarized for you, such as the percentage of time spent in a service, and whether operations failed. Trace view screenshot The Zipkin UI also presents a dependency diagram showing how many traced requests went through each application. This can be helpful for identifying aggregate behavior including error paths or calls to deprecated services. Dependency graph screenshot Application’s need to be “instrumented” to report trace data to Zipkin. This usually means configuration of a [tracer or instrumentation library](https://zipkin.io/pages/tracers_instrumentation.html). The most popular ways to report data to Zipkin are via http or Kafka, though many other options exist, such as Apache ActiveMQ, gRPC, RabbitMQ and Apache Pulsar. The data served to the UI is stored in-memory, or persistently with a supported backend such as Apache Cassandra or Elasticsearch. ## Quick-start The quickest way to get started is to fetch the [latest released server](https://central.sonatype.com/search?q=zipkin&namespace=io.zipkin&name=zipkin-server&sort=published) as a self-contained executable jar. Note that the Zipkin server requires minimum JRE 17+. For example: ```bash curl -sSL https://zipkin.io/quickstart.sh | bash -s java -jar zipkin.jar ``` You can also start Zipkin via Docker. ```bash # Note: this is mirrored as ghcr.io/openzipkin/zipkin docker run -d -p 9411:9411 openzipkin/zipkin ``` Once the server is running, you can view traces with the Zipkin UI at http://localhost:9411/zipkin. If your applications aren't sending traces, yet, configure them with [Zipkin instrumentation](https://zipkin.io/pages/tracers_instrumentation) or try one of our [examples](https://github.com/openzipkin?utf8=%E2%9C%93&q=example). Check out the [`zipkin-server`](zipkin-server/README.md) documentation for configuration details, or [Docker examples](docker/examples) for how to use docker-compose. ### Zipkin Slim The slim build of Zipkin is smaller and starts faster. It supports in-memory and Elasticsearch storage, but doesn't support messaging transports like Kafka or RabbitMQ. If these constraints match your needs, you can try slim like below: Running via Java: ```bash curl -sSL https://zipkin.io/quickstart.sh | bash -s io.zipkin:zipkin-server:LATEST:slim zipkin.jar java -jar zipkin.jar ``` Running via Docker: ```bash # Note: this is mirrored as ghcr.io/openzipkin/zipkin-slim docker run -d -p 9411:9411 openzipkin/zipkin-slim ``` Running via [Homebrew](https://formulae.brew.sh/formula/zipkin): ```bash brew install zipkin # to run in foreground zipkin # to run in background brew services start zipkin ``` ## Core Library The [core library](zipkin/src/main/java/zipkin2) is used by both Zipkin instrumentation and the Zipkin server. This includes built-in codec for Zipkin's v1 and v2 json formats. A direct dependency on gson (json library) is avoided by minifying and repackaging classes used. The result is a 155k jar which won't conflict with any library you use. Ex. ```java // All data are recorded against the same endpoint, associated with your service graph localEndpoint = Endpoint.newBuilder().serviceName("tweetie").ip("192.168.0.1").build() span = Span.newBuilder() .traceId("d3d200866a77cc59") .id("d3d200866a77cc59") .name("targz") .localEndpoint(localEndpoint) .timestamp(epochMicros()) .duration(durationInMicros) .putTag("compression.level", "9"); // Now, you can encode it as json bytes = SpanBytesEncoder.JSON_V2.encode(span); ``` Note: The above is just an example, most likely you'll want to use an existing tracing library like [Brave](https://github.com/openzipkin/brave) ### Core Library Requires Java 8+ The minimum Java language level of the core library is 8. This helps support those writing agent instrumentation. Version 2.x was the last to support Java 6. *Note*: [zipkin-reporter-brave](https://github.com/openzipkin/zipkin-reporter-java/blob/master/brave/README.md) does not use this library. So, [brave](https://github.com/openzipkin/brave) still supports Java 6. ## Storage Component Zipkin includes a [StorageComponent](zipkin/src/main/java/zipkin2/storage/StorageComponent.java), used to store and query spans and dependency links. This is used by the server and those making collectors, or span reporters. For this reason, storage components have minimal dependencies, though require Java 17+. Ex. ```java // this won't create network connections storage = ElasticsearchStorage.newBuilder() .hosts(asList("http://myelastic:9200")).build(); // prepare a call traceCall = storage.spanStore().getTrace("d3d200866a77cc59"); // execute it synchronously or asynchronously trace = traceCall.execute(); // clean up any sessions, etc storage.close(); ``` ### In-Memory The [InMemoryStorage](zipkin-server#in-memory-storage) component is packaged in zipkin's core library. It is neither persistent, nor viable for realistic work loads. Its purpose is for testing, for example starting a server on your laptop without any database needed. ### Cassandra The [Cassandra](zipkin-server#cassandra-storage) component uses Cassandra 3.11.3+ features, but is tested against the latest patch of Cassandra 4.1. This is the second generation of our Cassandra schema. It stores spans using UDTs, such that they appear like Zipkin v2 json in cqlsh. It is designed for scale, and uses a combination of SASI and manually implemented indexes to make querying larger data more performant. Note: This store requires a [job to aggregate](https://github.com/openzipkin/zipkin-dependencies) dependency links. ### Elasticsearch The [Elasticsearch](zipkin-server#elasticsearch-storage) component uses Elasticsearch 5+ features, but is tested against Elasticsearch 7-8.x and OpenSearch 2.x. It stores spans as Zipkin v2 json so that integration with other tools is straightforward. To help with scale, this uses a combination of custom and manually implemented indexing. Note: This store requires a [spark job](https://github.com/openzipkin/zipkin-dependencies) to aggregate dependency links. ### Disabling search The following API endpoints provide search features, and are enabled by default. Search primarily allows the trace list screen of the UI operate. * `GET /services` - Distinct Span.localServiceName * `GET /remoteServices?serviceName=X` - Distinct Span.remoteServiceName by Span.localServiceName * `GET /spans?serviceName=X` - Distinct Span.name by Span.localServiceName * `GET /autocompleteKeys` - Distinct keys of Span.tags subject to configurable whitelist * `GET /autocompleteValues?key=X` - Distinct values of Span.tags by key * `GET /traces` - Traces matching a query possibly including the above criteria When search is disabled, traces can only be retrieved by ID (`GET /trace/{traceId}`). Disabling search is only viable when there is an alternative way to find trace IDs, such as logs. Disabling search can reduce storage costs or increase write throughput. `StorageComponent.Builder.searchEnabled(false)` is implied when a zipkin is run with the env variable `SEARCH_ENABLED=false`. ### Legacy (v1) components The following components are no longer encouraged, but exist to help aid transition to supported ones. These are indicated as "v1" as they use data layouts based on Zipkin's V1 Thrift model, as opposed to the simpler v2 data model currently used. #### MySQL The [MySQL v1](zipkin-storage/mysql-v1) component uses MySQL 5.6+ features, but is tested against MariaDB 10.11. The schema was designed to be easy to understand and get started with; it was not designed for performance. Ex spans fields are columns, so you can perform ad-hoc queries using SQL. However, this component has [known performance issues](https://github.com/openzipkin/zipkin/issues/1233): queries will eventually take seconds to return if you put a lot of data into it. This store does not require a [job to aggregate](https://github.com/openzipkin/zipkin-dependencies) dependency links. However, running the job will improve performance of dependencies queries. ## Running the server from source The [Zipkin server](zipkin-server) receives spans via HTTP POST and respond to queries from its UI. It can also run collectors, such as RabbitMQ or Kafka. To run the server from the currently checked out source, enter the following. JDK 17+ is required to compile the source. ```bash # Build the server and also make its dependencies $ ./mvnw -q --batch-mode -DskipTests --also-make -pl zipkin-server clean install # Run the server $ java -jar ./zipkin-server/target/zipkin-server-*exec.jar ``` ## Artifacts Server artifacts are under the maven group id `io.zipkin` Library artifacts are under the maven group id `io.zipkin.zipkin2` ### Library Releases Releases are at [Sonatype](https://oss.sonatype.org/content/repositories/releases) and [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.zipkin%22) ### Library Snapshots Snapshots are uploaded to [Sonatype](https://oss.sonatype.org/content/repositories/snapshots) after commits to master. ### Docker Images Released versions of zipkin-server are published to Docker Hub as `openzipkin/zipkin` and GitHub Container Registry as `ghcr.io/openzipkin/zipkin`. See [docker](docker) for details. ### Helm Charts Helm charts are available via `helm repo add zipkin https://zipkin.io/zipkin-helm`. See [zipkin-helm](https://github.com/openzipkin/zipkin-helm) for details. ### Javadocs https://zipkin.io/zipkin contains versioned folders with JavaDocs published on each (non-PR) build, as well as releases. ================================================ FILE: RELEASE.md ================================================ # OpenZipkin Release Process This repo uses semantic versions. Please keep this in mind when choosing version numbers. 1. **Verify all dependencies are up-to-date** Before you start a release, make sure all dependencies are up-to-date, or are documented why not. Pay special attention to the [security workflow](.github/workflows/security.yml), which should run clean. 1. **Alert others you are releasing** There should be no commits made to master while the release is in progress (about 10 minutes). Before you start a release, alert others on [gitter](https://gitter.im/openzipkin/zipkin) so that they don't accidentally merge anything. If they do, and the build fails because of that, you'll have to recreate the release tag described below. 1. **Push a git tag** The trigger format is `release-MAJOR.MINOR.PATCH`, ex `git tag release-1.18.1 && git push origin release-1.18.1`. 1. **Wait for CI** The `release-MAJOR.MINOR.PATCH` tag triggers [`build-bin/maven/maven_release`](build-bin/maven/maven_release), which creates commits, `MAJOR.MINOR.PATCH` tag, and increments the version (maven-release-plugin). The `MAJOR.MINOR.PATCH` tag triggers [`build-bin/deploy`](build-bin/deploy), which does the following: * Publishes jars to https://oss.sonatype.org/content/repositories/releases [`build-bin/maven/maven_deploy`](build-bin/maven/maven_deploy) * Later, the same jars synchronize to Maven Central * Publishes Javadoc to https://zipkin.io/brave into a versioned subdirectory * Pushes images to Docker registries [`build-bin/docker_push`](build-bin/docker_push) Notes: * https://search.maven.org/ index will take longer than direct links like https://repo1.maven.org/maven2/io/zipkin ## Credentials The release process uses various credentials. If you notice something failing due to unauthorized, look at the notes in [.github/workflows/deploy.yml] and check the [org secrets](https://github.com/organizations/openzipkin/settings/secrets/actions). ### Troubleshooting invalid credentials If you receive a '401 unauthorized' failure from OSSRH, it is likely `SONATYPE_USER` or `SONATYPE_PASSWORD` entries are invalid, or possibly the user associated with them does not have rights to upload. The least destructive test is to try to publish a snapshot manually. By passing the values CI would use, you can kick off a snapshot from your laptop. This is a good way to validate that your unencrypted credentials are authorized. Here's an example of a snapshot deploy with specified credentials. ```bash $ export GPG_TTY=$(tty) && GPG_PASSPHRASE=whackamole SONATYPE_USER=adrianmole SONATYPE_PASSWORD=ed6f20bde9123bbb2312b221 build-bin/build-bin/maven/maven_deploy ``` ## Manually releasing If for some reason, you lost access to CI or otherwise cannot get automation to work, bear in mind this is a normal maven project, and can be released accordingly. *Note:* If [Sonatype is down](https://status.sonatype.com/), the below will not work. ```bash # First, set variable according to your personal credentials. These would normally be assigned as # org secrets: https://github.com/organizations/openzipkin/settings/secrets/actions export GPG_TTY=$(tty) export GPG_PASSPHRASE=your_gpg_passphrase export SONATYPE_USER=your_sonatype_account export SONATYPE_PASSWORD=your_sonatype_password release_version=xx-version-to-release-xx # now from latest master, create the release. This creates and pushes the MAJOR.MINOR.PATCH tag ./build-bin/maven/maven_release release-${release_version} # once this works, deploy the release git checkout ${release_version} ./build-bin/deploy # Finally, clean up ./mvnw release:clean git checkout master git reset HEAD --hard ``` ================================================ FILE: SECURITY.md ================================================ # OpenZipkin Security Process This document outlines the process for handling security concerns in OpenZipkin projects. Any vulnerability or misconfiguration detected in our [security workflow](.github/workflows/security.yml) should be addressed as a normal pull request. OpenZipkin is a volunteer community and does not have a dedicated security team. There may be periods where no volunteer is able to address a security concern. There is no SLA or warranty offered by volunteers. If you are a security researcher, please consider this before escalating. For security concerns that are sensitive or otherwise outside the scope of public issues, please contact zipkin-admin@googlegroups.com. ================================================ FILE: benchmarks/README.md ================================================ # zipkin-benchmarks This module includes [JMH](http://openjdk.java.net/projects/code-tools/jmh/) benchmarks for zipkin. You can use these to measure overhead. ### Running the benchmark From the project directory, run this to build the benchmarks: ```bash $ ./mvnw install -pl benchmarks -am -Dmaven.test.skip.exec=true ``` and the following to run them: ```bash $ java -jar benchmarks/target/benchmarks.jar ``` ================================================ FILE: benchmarks/pom.xml ================================================ 4.0.0 io.zipkin zipkin-parent 3.6.0-SNAPSHOT benchmarks Benchmarks Benchmarks (JMH) ${project.basedir}/.. 17 17 17 1.37 ${project.build.directory}/main/proto io.netty netty-bom ${netty.version} pom import com.fasterxml.jackson jackson-bom ${jackson.version} pom import com.squareup.wire wire-runtime-jvm ${wire.version} provided io.zipkin.proto3 zipkin-proto3 ${zipkin-proto3.version} provided org.openjdk.jmh jmh-core ${jmh.version} test org.openjdk.jmh jmh-generator-annprocess ${jmh.version} test com.google.code.gson gson ${gson.version} test com.squareup.moshi moshi 1.15.2 com.squareup.okio okio test com.esotericsoftware kryo ${kryo.version} test com.google.protobuf protobuf-java 3.25.8 test ${project.groupId} zipkin-server ${project.version} org.slf4j * org.springframework.boot spring-boot-starter-log4j2 test org.slf4j slf4j-simple ${slf4j.version} test org.slf4j jul-to-slf4j ${slf4j.version} ${project.groupId}.zipkin2 zipkin-storage-elasticsearch ${project.version} test ${project.groupId}.zipkin2 zipkin-tests ${project.version} test ${project.groupId}.zipkin2 zipkin-storage-cassandra ${project.version} io.netty * test org.apache.cassandra java-driver-core ${java-driver.version} test io.netty * ${project.groupId}.zipkin2 zipkin-storage-mysql-v1 ${project.version} test org.mariadb.jdbc mariadb-java-client ${mariadb-java-client.version} test com.zaxxer HikariCP ${HikariCP.version} test org.testcontainers testcontainers ${testcontainers.version} test maven-assembly-plugin ${maven-assembly-plugin.version} src/test/assembly/test-jar.xml make-assembly package single true benchmarks false org.openjdk.jmh.Main maven-compiler-plugin default-compile compile compile javac test-compile test-compile testCompile javac org.openjdk.jmh jmh-generator-annprocess ${jmh.version} maven-dependency-plugin de.m3y.maven wire-maven-plugin ${generated-proto.directory} ================================================ FILE: benchmarks/src/test/assembly/test-jar.xml ================================================ test-jar jar false / true true test ${project.build.directory}/test-classes / **/* true ================================================ FILE: benchmarks/src/test/java/zipkin2/EndpointBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(2) public class EndpointBenchmarks { static final String IPV4 = "43.0.192.2", IPV6 = "2001:db8::c001"; static final InetAddress IPV4_ADDR, IPV6_ADDR; static { try { IPV4_ADDR = Inet4Address.getByName(IPV4); IPV6_ADDR = Inet6Address.getByName(IPV6); } catch (UnknownHostException e) { throw new AssertionError(e); } } Endpoint.Builder builder = Endpoint.newBuilder(); @Benchmark public boolean parseIpv4_literal() { return builder.parseIp(IPV4); } @Benchmark public boolean parseIpv4_addr() { return builder.parseIp(IPV4_ADDR); } @Benchmark public boolean parseIpv4_bytes() { return builder.parseIp(IPV4_ADDR.getAddress()); } @Benchmark public boolean parseIpv6_literal() { return builder.parseIp(IPV6); } @Benchmark public boolean parseIpv6_addr() { return builder.parseIp(IPV6_ADDR); } @Benchmark public boolean parseIpv6_bytes() { return builder.parseIp(IPV6_ADDR.getAddress()); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .addProfiler("gc") .include(".*" + EndpointBenchmarks.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/SpanBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.serializers.JavaSerializer; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import static zipkin2.internal.HexCodec.lowerHexToUnsignedLong; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(2) public class SpanBenchmarks { static final Endpoint FRONTEND = Endpoint.newBuilder().serviceName("frontend").ip("127.0.0.1").build(); static final Endpoint BACKEND = Endpoint.newBuilder().serviceName("backend").ip("192.168.99.101").port(9000).build(); static final Span clientSpan = buildClientSpan(Span.newBuilder()); final Span.Builder sharedBuilder; public SpanBenchmarks() { sharedBuilder = buildClientSpan().toBuilder(); } static final String traceIdHex = "86154a4ba6e91385", spanIdHex = "4d1e00c0db9010db"; static final long traceId = lowerHexToUnsignedLong(traceIdHex); static final long spanId = lowerHexToUnsignedLong(spanIdHex); @Benchmark public Span buildClientSpan() { return buildClientSpan(Span.newBuilder()); } @Benchmark public Span buildClientSpan_longs() { return buildClientSpan_longs(Span.newBuilder()); } static Span buildClientSpan(Span.Builder builder) { return builder .traceId(traceIdHex) .parentId(traceIdHex) .id(spanIdHex) .name("get") .kind(Span.Kind.CLIENT) .localEndpoint(FRONTEND) .remoteEndpoint(BACKEND) .timestamp(1472470996199000L) .duration(207000L) .addAnnotation(1472470996238000L, "ws") .addAnnotation(1472470996403000L, "wr") .putTag("http.path", "/api") .putTag("clnt/finagle.version", "6.45.0") .build(); } static Span buildClientSpan_longs(Span.Builder builder) { return builder .traceId(0L, traceId) .parentId(traceId) .id(spanId) .name("get") .kind(Span.Kind.CLIENT) .localEndpoint(FRONTEND) .remoteEndpoint(BACKEND) .timestamp(1472470996199000L) .duration(207000L) .addAnnotation(1472470996238000L, "ws") .addAnnotation(1472470996403000L, "wr") .putTag("http.path", "/api") .putTag("clnt/finagle.version", "6.45.0") .build(); } @Benchmark public Span buildClientSpan_clear() { return buildClientSpan(sharedBuilder.clear()); } @Benchmark public Span buildClientSpan_clone() { return sharedBuilder.clone().build(); } static final Kryo kryo = new Kryo(); static final byte[] clientSpanSerialized; static { kryo.register(Span.class, new JavaSerializer()); Output output = new Output(4096); kryo.writeObject(output, clientSpan); output.flush(); clientSpanSerialized = output.getBuffer(); } /** manually implemented with json so not as slow as normal java */ @Benchmark public Span serialize_kryo() { return kryo.readObject(new Input(clientSpanSerialized), Span.class); } @Benchmark public byte[] deserialize_kryo() { Output output = new Output(clientSpanSerialized.length); kryo.writeObject(output, clientSpan); output.flush(); return output.getBuffer(); } @Benchmark public String padLeft_1Char() { return Span.padLeft("1", 16); } @Benchmark public String padLeft_15Chars() { return Span.padLeft("123456789012345", 16); } @Benchmark public String padLeft_17Chars() { return Span.padLeft("12345678901234567", 32); } @Benchmark public String padLeft_31Chars() { return Span.padLeft("1234567890123456789012345678901", 32); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + SpanBenchmarks.class.getSimpleName() + ".*") .addProfiler("gc") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/CodecBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import zipkin2.Span; import static java.nio.charset.StandardCharsets.UTF_8; import static zipkin2.storage.cassandra.internal.Resources.resourceToString; /** * The {@link SpanBytesEncoder bundled java codec} aims to be both small in size (i.e. does not * significantly increase the size of zipkin's jar), and efficient. It may not always be fastest, * but we should try to keep it competitive. * *

Note that the wire benchmarks use their structs, not ours. This will result in more efficient * writes as there's no hex codec of IDs, stringifying of IPs etc. A later change could do that, but * it likely still going to be more efficient than our dependency-free codec. This means in cases * where extra dependencies are ok (such as our server), we could consider using wire. */ @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(1) public class CodecBenchmarks { static final byte[] clientSpanJsonV2 = resourceToString("/zipkin2-client.json").getBytes(UTF_8); static final Span clientSpan = SpanBytesDecoder.JSON_V2.decodeOne(clientSpanJsonV2); static final byte[] clientSpanJsonV1 = SpanBytesEncoder.JSON_V1.encode(clientSpan); static final byte[] clientSpanProto3 = SpanBytesEncoder.PROTO3.encode(clientSpan); static final byte[] clientSpanThrift = SpanBytesEncoder.THRIFT.encode(clientSpan); static final List tenClientSpans = Collections.nCopies(10, clientSpan); static final byte[] tenClientSpansJsonV2 = SpanBytesEncoder.JSON_V2.encodeList(tenClientSpans); @Benchmark public Span decodeClientSpan_JSON_V1() { return SpanBytesDecoder.JSON_V1.decodeOne(clientSpanJsonV1); } @Benchmark public Span decodeClientSpan_JSON_V2() { return SpanBytesDecoder.JSON_V2.decodeOne(clientSpanJsonV2); } @Benchmark public Span decodeClientSpan_PROTO3() { return SpanBytesDecoder.PROTO3.decodeOne(clientSpanProto3); } @Benchmark public Span decodeClientSpan_THRIFT() { return SpanBytesDecoder.THRIFT.decodeOne(clientSpanThrift); } @Benchmark public int sizeInBytesClientSpan_JSON_V2() { return SpanBytesEncoder.JSON_V2.sizeInBytes(clientSpan); } @Benchmark public int sizeInBytesClientSpan_JSON_V1() { return SpanBytesEncoder.JSON_V1.sizeInBytes(clientSpan); } @Benchmark public int sizeInBytesClientSpan_PROTO3() { return SpanBytesEncoder.PROTO3.sizeInBytes(clientSpan); } @Benchmark public int sizeInBytesClientSpan_THRIFT() { return SpanBytesEncoder.THRIFT.sizeInBytes(clientSpan); } @Benchmark public byte[] writeClientSpan_JSON_V2() { return SpanBytesEncoder.JSON_V2.encode(clientSpan); } @Benchmark public byte[] writeClientSpan_JSON_V1() { return SpanBytesEncoder.JSON_V1.encode(clientSpan); } @Benchmark public byte[] writeClientSpan_PROTO3() { return SpanBytesEncoder.PROTO3.encode(clientSpan); } @Benchmark public byte[] writeClientSpan_THRIFT() { return SpanBytesEncoder.THRIFT.encode(clientSpan); } @Benchmark public List decodeTenClientSpans_JSON_V2() { return SpanBytesDecoder.JSON_V2.decodeList(tenClientSpansJsonV2); } @Benchmark public byte[] writeTenClientSpans_JSON_V2() { return SpanBytesEncoder.JSON_V2.encodeList(tenClientSpans); } static final byte[] chineseSpanJsonV2 = resourceToString("/zipkin2-chinese.json").getBytes(UTF_8); static final Span chineseSpan = SpanBytesDecoder.JSON_V2.decodeOne(chineseSpanJsonV2); static final byte[] chineseSpanProto3 = SpanBytesEncoder.PROTO3.encode(chineseSpan); static final byte[] chineseSpanJsonV1 = SpanBytesEncoder.JSON_V1.encode(chineseSpan); static final byte[] chineseSpanThrift = SpanBytesEncoder.THRIFT.encode(chineseSpan); @Benchmark public Span decodeChineseSpan_JSON_V1() { return SpanBytesDecoder.JSON_V1.decodeOne(chineseSpanJsonV1); } @Benchmark public Span decodeChineseSpan_JSON_V2() { return SpanBytesDecoder.JSON_V2.decodeOne(chineseSpanJsonV2); } @Benchmark public Span decodeChineseSpan_PROTO3() { return SpanBytesDecoder.PROTO3.decodeOne(chineseSpanProto3); } @Benchmark public Span decodeChineseSpan_THRIFT() { return SpanBytesDecoder.THRIFT.decodeOne(chineseSpanThrift); } @Benchmark public byte[] writeChineseSpan_JSON_V2() { return SpanBytesEncoder.JSON_V2.encode(chineseSpan); } @Benchmark public byte[] writeChineseSpan_JSON_V1() { return SpanBytesEncoder.JSON_V1.encode(chineseSpan); } @Benchmark public byte[] writeChineseSpan_PROTO3() { return SpanBytesEncoder.PROTO3.encode(chineseSpan); } @Benchmark public byte[] writeChineseSpan_THRIFT() { return SpanBytesEncoder.THRIFT.encode(chineseSpan); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .addProfiler("gc") .include(".*" + CodecBenchmarks.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/JacksonSpanDecoder.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import zipkin2.Annotation; import zipkin2.Endpoint; import zipkin2.Span; import zipkin2.internal.ReadBuffer; public final class JacksonSpanDecoder { static final JsonFactory JSON_FACTORY = new JsonFactory(); public static List decodeList(byte[] spans) { try { return decodeList(JSON_FACTORY.createParser(spans)); } catch (IOException e) { throw new UncheckedIOException(e); } } public static List decodeList(ByteBuffer spans) { try { return decodeList(JSON_FACTORY.createParser(ReadBuffer.wrapUnsafe(spans))); } catch (IOException e) { throw new UncheckedIOException(e); } } public static Span decodeOne(byte[] span) { try { return decodeOne(JSON_FACTORY.createParser(span)); } catch (IOException e) { throw new UncheckedIOException(e); } } public static Span decodeOne(ByteBuffer span) { try { return decodeOne(JSON_FACTORY.createParser(ReadBuffer.wrapUnsafe(span))); } catch (IOException e) { throw new UncheckedIOException(e); } } static List decodeList(JsonParser jsonParser) { List out = new ArrayList<>(); try { if (jsonParser.nextToken() != JsonToken.START_ARRAY) { throw new IOException("Not a valid JSON array, start token: " + jsonParser.currentToken()); } while (jsonParser.nextToken() != JsonToken.END_ARRAY) { out.add(parseSpan(jsonParser)); } } catch (IOException e) { throw new UncheckedIOException(e); } return out; } static Span decodeOne(JsonParser jsonParser) { try { if (!jsonParser.hasCurrentToken()) { jsonParser.nextToken(); } return parseSpan(jsonParser); } catch (IOException e) { throw new UncheckedIOException(e); } } static Span parseSpan(JsonParser jsonParser) throws IOException { if (!jsonParser.isExpectedStartObjectToken()) { throw new IOException("Not a valid JSON object, start token: " + jsonParser.currentToken()); } Span.Builder result = Span.newBuilder(); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.currentName(); JsonToken value = jsonParser.nextToken(); if (value == JsonToken.VALUE_NULL) { continue; } switch (fieldName) { case "traceId": result.traceId(jsonParser.getValueAsString()); break; case "parentId": result.parentId(jsonParser.getValueAsString()); break; case "id": result.id(jsonParser.getValueAsString()); break; case "kind": result.kind(Span.Kind.valueOf(jsonParser.getValueAsString())); break; case "name": result.name(jsonParser.getValueAsString()); break; case "timestamp": result.timestamp(jsonParser.getValueAsLong()); break; case "duration": result.duration(jsonParser.getValueAsLong()); break; case "localEndpoint": result.localEndpoint(parseEndpoint(jsonParser)); break; case "remoteEndpoint": result.remoteEndpoint(parseEndpoint(jsonParser)); break; case "annotations": if (!jsonParser.isExpectedStartArrayToken()) { throw new IOException("Invalid span, expecting annotations array start, got: " + value); } while (jsonParser.nextToken() != JsonToken.END_ARRAY) { Annotation a = parseAnnotation(jsonParser); result.addAnnotation(a.timestamp(), a.value()); } break; case "tags": if (value != JsonToken.START_OBJECT) { throw new IOException("Invalid span, expecting tags object, got: " + value); } while (jsonParser.nextToken() != JsonToken.END_OBJECT) { result.putTag(jsonParser.currentName(), jsonParser.nextTextValue()); } break; case "debug": result.debug(jsonParser.getBooleanValue()); break; case "shared": result.shared(jsonParser.getBooleanValue()); break; default: jsonParser.skipChildren(); } } return result.build(); } static Endpoint parseEndpoint(JsonParser jsonParser) throws IOException { if (!jsonParser.isExpectedStartObjectToken()) { throw new IOException("Not a valid JSON object, start token: " + jsonParser.currentToken()); } String serviceName = null, ipv4 = null, ipv6 = null; int port = 0; while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.currentName(); JsonToken value = jsonParser.nextToken(); if (value == JsonToken.VALUE_NULL) { continue; } switch (fieldName) { case "serviceName": serviceName = jsonParser.getValueAsString(); break; case "ipv4": ipv4 = jsonParser.getValueAsString(); break; case "ipv6": ipv6 = jsonParser.getValueAsString(); break; case "port": port = jsonParser.getValueAsInt(); break; default: jsonParser.skipChildren(); } } if (serviceName == null && ipv4 == null && ipv6 == null && port == 0) return null; return Endpoint.newBuilder() .serviceName(serviceName) .ip(ipv4) .ip(ipv6) .port(port) .build(); } static Annotation parseAnnotation(JsonParser jsonParser) throws IOException { if (!jsonParser.isExpectedStartObjectToken()) { throw new IOException("Not a valid JSON object, start token: " + jsonParser.currentToken()); } long timestamp = 0; String value = null; while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.currentName(); switch (fieldName) { case "timestamp": timestamp = jsonParser.getValueAsLong(); break; case "value": value = jsonParser.getValueAsString(); break; default: jsonParser.skipChildren(); } } if (timestamp == 0 || value == null) { throw new IllegalStateException("Incomplete annotation at " + jsonParser.currentToken()); } return Annotation.create(timestamp, value); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/JacksonSpanDecoderTest.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static zipkin2.TestObjects.CLIENT_SPAN; import static zipkin2.TestObjects.TRACE; public class JacksonSpanDecoderTest { byte[] encoded = SpanBytesEncoder.JSON_V2.encodeList(TRACE); byte[] encodedSpan = SpanBytesEncoder.JSON_V2.encode(CLIENT_SPAN); @Test void decodeList_bytes() { assertThat(JacksonSpanDecoder.decodeList(encoded)) .isEqualTo(TRACE); } @Test void decodeList_byteBuffer() { ByteBuf encodedBuf = PooledByteBufAllocator.DEFAULT.buffer(encoded.length); encodedBuf.writeBytes(encoded); try { assertThat(JacksonSpanDecoder.decodeList(encodedBuf.nioBuffer())) .isEqualTo(TRACE); } finally { encodedBuf.release(); } } @Test void decodeOne() { assertThat(JacksonSpanDecoder.decodeOne(encodedSpan)) .isEqualTo(CLIENT_SPAN); } @Test void decodeOne_byteBuffer() { ByteBuf encodedBuf = PooledByteBufAllocator.DEFAULT.buffer(encodedSpan.length); encodedBuf.writeBytes(encodedSpan); try { assertThat(JacksonSpanDecoder.decodeOne(encodedBuf.nioBuffer())) .isEqualTo(CLIENT_SPAN); } finally { encodedBuf.release(); } } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/JsonCodecBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import zipkin2.Span; import static java.nio.charset.StandardCharsets.UTF_8; import static zipkin2.storage.cassandra.internal.Resources.resourceToString; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(1) public class JsonCodecBenchmarks { static final MoshiSpanDecoder MOSHI = MoshiSpanDecoder.create(); static final byte[] clientSpanJsonV2 = resourceToString("/zipkin2-client.json").getBytes(UTF_8); static final Span clientSpan = SpanBytesDecoder.JSON_V2.decodeOne(clientSpanJsonV2); // Assume a message is 1000 spans (which is a high number for as this is per-node-second) static final List spans = Collections.nCopies(1000, clientSpan); static final byte[] encodedBytes = SpanBytesEncoder.JSON_V2.encodeList(spans); private ByteBuf encodedBuf; @Setup public void setup() { encodedBuf = PooledByteBufAllocator.DEFAULT.buffer(encodedBytes.length); encodedBuf.writeBytes(encodedBytes); } @TearDown public void tearDown() { encodedBuf.release(); } @Benchmark public List bytes_jacksonDecoder() { return JacksonSpanDecoder.decodeList(encodedBytes); } @Benchmark public List bytes_moshiDecoder() { return MOSHI.decodeList(encodedBytes); } @Benchmark public List bytes_zipkinDecoder() { return SpanBytesDecoder.JSON_V2.decodeList(encodedBytes); } @Benchmark public List bytebuffer_jacksonDecoder() { return JacksonSpanDecoder.decodeList(encodedBuf.nioBuffer()); } @Benchmark public List bytebuffer_moshiDecoder() { return MOSHI.decodeList(encodedBuf.nioBuffer()); } @Benchmark public List bytebuffer_zipkinDecoder() { return SpanBytesDecoder.JSON_V2.decodeList(encodedBuf.nioBuffer()); } // Convenience main entry-point public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .include(".*" + JsonCodecBenchmarks.class.getSimpleName() + ".*") .addProfiler("gc") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/MoshiSpanDecoder.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import okio.Buffer; import okio.BufferedSource; import okio.Okio; import okio.Timeout; import zipkin2.Annotation; import zipkin2.Endpoint; import zipkin2.Span; import zipkin2.internal.Nullable; /** * Read-only json adapters resurrected from before we switched to Java 6 as storage components can * be Java 7+ */ public final class MoshiSpanDecoder { final JsonAdapter> listSpansAdapter; public static MoshiSpanDecoder create() { return new MoshiSpanDecoder(); } MoshiSpanDecoder() { listSpansAdapter = new Moshi.Builder() .add(Span.class, SPAN_ADAPTER) .build().adapter(Types.newParameterizedType(List.class, Span.class)); } public List decodeList(byte[] spans) { BufferedSource source = new Buffer().write(spans); try { return listSpansAdapter.fromJson(source); } catch (IOException e) { throw new AssertionError(e); // no I/O } } public List decodeList(ByteBuffer spans) { try { return listSpansAdapter.fromJson(JsonReader.of(Okio.buffer(new ByteBufferSource(spans)))); } catch (IOException e) { throw new AssertionError(e); // no I/O } } static final class ByteBufferSource implements okio.Source { final ByteBuffer source; final Buffer.UnsafeCursor cursor = new Buffer.UnsafeCursor(); ByteBufferSource(ByteBuffer source) { this.source = source; } @Override public long read(Buffer sink, long byteCount) { try (Buffer.UnsafeCursor ignored = sink.readAndWriteUnsafe(cursor)) { long oldSize = sink.size(); int length = (int) Math.min(source.remaining(), Math.min(8192, byteCount)); if (length == 0) return -1; cursor.expandBuffer(length); source.get(cursor.data, cursor.start, length); cursor.resizeBuffer(oldSize + length); return length; } } @Override public Timeout timeout() { return Timeout.NONE; } @Override public void close() { } } public static final JsonAdapter SPAN_ADAPTER = new JsonAdapter() { @Override public Span fromJson(JsonReader reader) throws IOException { Span.Builder result = Span.newBuilder(); reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if (reader.peek() == JsonReader.Token.NULL) { reader.skipValue(); continue; } switch (nextName) { case "traceId": result.traceId(reader.nextString()); break; case "parentId": result.parentId(reader.nextString()); break; case "id": result.id(reader.nextString()); break; case "kind": result.kind(Span.Kind.valueOf(reader.nextString())); break; case "name": result.name(reader.nextString()); break; case "timestamp": result.timestamp(reader.nextLong()); break; case "duration": result.duration(reader.nextLong()); break; case "localEndpoint": result.localEndpoint(ENDPOINT_ADAPTER.fromJson(reader)); break; case "remoteEndpoint": result.remoteEndpoint(ENDPOINT_ADAPTER.fromJson(reader)); break; case "annotations": reader.beginArray(); while (reader.hasNext()) { Annotation a = ANNOTATION_ADAPTER.fromJson(reader); result.addAnnotation(a.timestamp(), a.value()); } reader.endArray(); break; case "tags": reader.beginObject(); while (reader.hasNext()) { result.putTag(reader.nextName(), reader.nextString()); } reader.endObject(); break; case "debug": result.debug(reader.nextBoolean()); break; case "shared": result.shared(reader.nextBoolean()); break; default: reader.skipValue(); } } reader.endObject(); return result.build(); } @Override public void toJson(JsonWriter writer, @Nullable Span value) { throw new UnsupportedOperationException(); } }; static final JsonAdapter ANNOTATION_ADAPTER = new JsonAdapter() { @Override public Annotation fromJson(JsonReader reader) throws IOException { reader.beginObject(); Long timestamp = null; String value = null; while (reader.hasNext()) { switch (reader.nextName()) { case "timestamp": timestamp = reader.nextLong(); break; case "value": value = reader.nextString(); break; default: reader.skipValue(); } } reader.endObject(); if (timestamp == null || value == null) { throw new IllegalStateException("Incomplete annotation at " + reader.getPath()); } return Annotation.create(timestamp, value); } @Override public void toJson(JsonWriter writer, @Nullable Annotation value) { throw new UnsupportedOperationException(); } }; static final JsonAdapter ENDPOINT_ADAPTER = new JsonAdapter() { @Override public Endpoint fromJson(JsonReader reader) throws IOException { reader.beginObject(); String serviceName = null, ipv4 = null, ipv6 = null; int port = 0; while (reader.hasNext()) { String nextName = reader.nextName(); if (reader.peek() == JsonReader.Token.NULL) { reader.skipValue(); continue; } switch (nextName) { case "serviceName": serviceName = reader.nextString(); break; case "ipv4": ipv4 = reader.nextString(); break; case "ipv6": ipv6 = reader.nextString(); break; case "port": port = reader.nextInt(); break; default: reader.skipValue(); } } reader.endObject(); if (serviceName == null && ipv4 == null && ipv6 == null && port == 0) return null; return Endpoint.newBuilder() .serviceName(serviceName) .ip(ipv4) .ip(ipv6) .port(port) .build(); } @Override public void toJson(JsonWriter writer, @Nullable Endpoint value) { throw new UnsupportedOperationException(); } }.nullSafe(); } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/MoshiSpanDecoderTest.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static zipkin2.TestObjects.TRACE; public class MoshiSpanDecoderTest { byte[] encoded = SpanBytesEncoder.JSON_V2.encodeList(TRACE); @Test void decodeList_bytes() { assertThat(new MoshiSpanDecoder().decodeList(encoded)) .isEqualTo(TRACE); } @Test void decodeList_byteBuffer() { ByteBuf encodedBuf = PooledByteBufAllocator.DEFAULT.buffer(encoded.length); encodedBuf.writeBytes(encoded); try { assertThat(new MoshiSpanDecoder().decodeList(encodedBuf.nioBuffer())) .isEqualTo(TRACE); } finally { encodedBuf.release(); } } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/ProtoCodecBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import zipkin2.Span; import static java.nio.charset.StandardCharsets.UTF_8; import static zipkin2.storage.cassandra.internal.Resources.resourceToString; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(1) public class ProtoCodecBenchmarks { static final byte[] clientSpanJsonV2 = resourceToString("/zipkin2-client.json").getBytes(UTF_8); static final Span clientSpan = SpanBytesDecoder.JSON_V2.decodeOne(clientSpanJsonV2); // Assume a message is 1000 spans (which is a high number for as this is per-node-second) static final List spans = Collections.nCopies(1000, clientSpan); static final byte[] encodedBytes = SpanBytesEncoder.PROTO3.encodeList(spans); private ByteBuf encodedBuf; @Setup public void setup() { encodedBuf = PooledByteBufAllocator.DEFAULT.buffer(encodedBytes.length); encodedBuf.writeBytes(encodedBytes); } @TearDown public void tearDown() { encodedBuf.release(); } @Benchmark public List bytes_zipkinDecoder() { return SpanBytesDecoder.PROTO3.decodeList(encodedBytes); } @Benchmark public List bytes_protobufDecoder() { return ProtobufSpanDecoder.decodeList(encodedBytes); } @Benchmark public List bytes_wireDecoder() { return WireSpanDecoder.decodeList(encodedBytes); } @Benchmark public List bytebuffer_zipkinDecoder() { return SpanBytesDecoder.PROTO3.decodeList(encodedBuf.nioBuffer()); } @Benchmark public List bytebuffer_protobufDecoder() { return ProtobufSpanDecoder.decodeList(encodedBuf.nioBuffer()); } @Benchmark public List bytebuffer_wireDecoder() { return WireSpanDecoder.decodeList(encodedBuf.nioBuffer()); } // Convenience main entry-point public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .include(".*" + ProtoCodecBenchmarks.class.getSimpleName()) .addProfiler("gc") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/ProtobufSpanDecoder.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import com.google.protobuf.CodedInputStream; import com.google.protobuf.WireFormat; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import zipkin2.Endpoint; import zipkin2.Span; import zipkin2.internal.RecyclableBuffers; public class ProtobufSpanDecoder { static final Logger LOG = Logger.getLogger(ProtobufSpanDecoder.class.getName()); static final boolean DEBUG = false; // map in proto is a special field with key, value static final int MAP_KEY_KEY = (1 << 3) | WireFormat.WIRETYPE_LENGTH_DELIMITED; static final int MAP_VALUE_KEY = (2 << 3) | WireFormat.WIRETYPE_LENGTH_DELIMITED; static boolean decodeTag(CodedInputStream input, Span.Builder span) throws IOException { // now, we are in the tag fields String key = null, value = ""; // empty tags allowed boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case MAP_KEY_KEY: { key = input.readStringRequireUtf8(); break; } case MAP_VALUE_KEY: { value = input.readStringRequireUtf8(); break; } default: { logAndSkip(input, tag); break; } } } if (key == null) return false; span.putTag(key, value); return true; } static boolean decodeAnnotation(CodedInputStream input, Span.Builder span) throws IOException { long timestamp = 0L; String value = null; boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case 9: { timestamp = input.readFixed64(); break; } case 18: { value = input.readStringRequireUtf8(); break; } default: { logAndSkip(input, tag); break; } } } if (timestamp == 0L || value == null) return false; span.addAnnotation(timestamp, value); return true; } private static Endpoint decodeEndpoint(CodedInputStream input) throws IOException { Endpoint.Builder endpoint = Endpoint.newBuilder(); boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case 10: { endpoint.serviceName(input.readStringRequireUtf8()); break; } case 18: case 26: { endpoint.parseIp(input.readByteArray()); break; } case 32: { endpoint.port(input.readInt32()); break; } default: { logAndSkip(input, tag); break; } } } return endpoint.build(); } public static Span decodeOne(CodedInputStream input) throws IOException { Span.Builder span = Span.newBuilder(); boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case 10: { span.traceId(readHexString(input)); break; } case 18: { span.parentId(readHexString(input)); break; } case 26: { span.id(readHexString(input)); break; } case 32: { int kind = input.readEnum(); if (kind == 0) break; if (kind > Span.Kind.values().length) break; span.kind(Span.Kind.values()[kind - 1]); break; } case 42: { span.name(input.readStringRequireUtf8()); break; } case 49: { span.timestamp(input.readFixed64()); break; } case 56: { span.duration(input.readUInt64()); break; } case 66: { int length = input.readRawVarint32(); int oldLimit = input.pushLimit(length); span.localEndpoint(decodeEndpoint(input)); input.checkLastTagWas(0); input.popLimit(oldLimit); break; } case 74: { int length = input.readRawVarint32(); int oldLimit = input.pushLimit(length); span.remoteEndpoint(decodeEndpoint(input)); input.checkLastTagWas(0); input.popLimit(oldLimit); break; } case 82: { int length = input.readRawVarint32(); int oldLimit = input.pushLimit(length); decodeAnnotation(input, span); input.checkLastTagWas(0); input.popLimit(oldLimit); break; } case 90: { int length = input.readRawVarint32(); int oldLimit = input.pushLimit(length); decodeTag(input, span); input.checkLastTagWas(0); input.popLimit(oldLimit); break; } case 96: { span.debug(input.readBool()); break; } case 104: { span.shared(input.readBool()); break; } default: { logAndSkip(input, tag); break; } } } return span.build(); } public static List decodeList(byte[] spans) { return decodeList(CodedInputStream.newInstance(spans)); } public static List decodeList(ByteBuffer spans) { return decodeList(CodedInputStream.newInstance(spans)); } public static List decodeList(CodedInputStream input) { ArrayList spans = new ArrayList<>(); try { boolean done = false; while (!done) { int tag = input.readTag(); switch (tag) { case 0: done = true; break; case 10: int length = input.readRawVarint32(); int oldLimit = input.pushLimit(length); spans.add(decodeOne(input)); input.checkLastTagWas(0); input.popLimit(oldLimit); break; default: { logAndSkip(input, tag); break; } } } } catch (IOException e) { throw new RuntimeException(e); } return spans; } static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static String readHexString(CodedInputStream input) throws IOException { int size = input.readRawVarint32(); int length = size * 2; // All our hex fields are at most 32 characters. if (length > 32) { throw new AssertionError("hex field greater than 32 chars long: " + length); } char[] result = RecyclableBuffers.shortStringBuffer(); for (int i = 0; i < length; i += 2) { byte b = input.readRawByte(); result[i] = HEX_DIGITS[(b >> 4) & 0xf]; result[i + 1] = HEX_DIGITS[b & 0xf]; } return new String(result, 0, length); } static void logAndSkip(CodedInputStream input, int tag) throws IOException { if (DEBUG) { // avoiding volatile reads as we don't log on skip in our normal codec int nextWireType = WireFormat.getTagWireType(tag); int nextFieldNumber = WireFormat.getTagFieldNumber(tag); LOG.fine("Skipping field: byte=%s, fieldNumber=%s, wireType=%s".formatted( input.getTotalBytesRead(), nextFieldNumber, nextWireType)); } input.skipField(tag); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/codec/WireSpanDecoder.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.codec; import com.google.protobuf.WireFormat; import com.squareup.wire.ProtoAdapter; import com.squareup.wire.ProtoReader; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import okio.Buffer; import okio.ByteString; import zipkin2.Endpoint; import zipkin2.Span; import zipkin2.internal.RecyclableBuffers; public class WireSpanDecoder { static final Logger LOG = Logger.getLogger(WireSpanDecoder.class.getName()); static final boolean DEBUG = false; static boolean decodeTag(ProtoReader input, Span.Builder span) throws IOException { // now, we are in the tag fields String key = null, value = ""; // empty tags allowed boolean done = false; while (!done) { int tag = input.nextTag(); switch (tag) { case -1: done = true; break; case 1: { key = input.readString(); break; } case 2: { value = input.readString(); break; } default: { logAndSkip(input, tag); break; } } } if (key == null) return false; span.putTag(key, value); return true; } static boolean decodeAnnotation(ProtoReader input, Span.Builder span) throws IOException { long timestamp = 0L; String value = null; boolean done = false; while (!done) { int tag = input.nextTag(); switch (tag) { case -1: done = true; break; case 1: { timestamp = input.readFixed64(); break; } case 2: { value = input.readString(); break; } default: { logAndSkip(input, tag); break; } } } if (timestamp == 0L || value == null) return false; span.addAnnotation(timestamp, value); return true; } private static Endpoint decodeEndpoint(ProtoReader input) throws IOException { Endpoint.Builder endpoint = Endpoint.newBuilder(); boolean done = false; while (!done) { int tag = input.nextTag(); switch (tag) { case -1: done = true; break; case 1: { String s = input.readString(); endpoint.serviceName(s); break; } case 2: case 3: { endpoint.parseIp(input.readBytes().toByteArray()); break; } case 4: { endpoint.port(input.readVarint32()); break; } default: { logAndSkip(input, tag); break; } } } return endpoint.build(); } public static Span decodeOne(ProtoReader input) throws IOException { Span.Builder span = Span.newBuilder(); boolean done = false; while (!done) { int tag = input.nextTag(); switch (tag) { case -1: done = true; break; case 1: { span.traceId(readHexString(input)); break; } case 2: { span.parentId(readHexString(input)); break; } case 3: { span.id(readHexString(input)); break; } case 4: { int kind = input.readVarint32(); if (kind == 0) break; if (kind > Span.Kind.values().length) break; span.kind(Span.Kind.values()[kind - 1]); break; } case 5: { String name = input.readString(); span.name(name); break; } case 6: { span.timestamp(input.readFixed64()); break; } case 7: { span.duration(input.readVarint64()); break; } case 8: { long token = input.beginMessage(); span.localEndpoint(decodeEndpoint(input)); input.endMessageAndGetUnknownFields(token); break; } case 9: { long token = input.beginMessage(); span.remoteEndpoint(decodeEndpoint(input)); input.endMessageAndGetUnknownFields(token); break; } case 10: { long token = input.beginMessage(); decodeAnnotation(input, span); input.endMessageAndGetUnknownFields(token); break; } case 11: { long token = input.beginMessage(); decodeTag(input, span); input.endMessageAndGetUnknownFields(token); break; } case 12: { span.debug(ProtoAdapter.BOOL.decode(input)); break; } case 13: { span.shared(ProtoAdapter.BOOL.decode(input)); break; } default: { logAndSkip(input, tag); break; } } } return span.build(); } public static List decodeList(byte[] spans) { return decodeList(new ProtoReader(new Buffer().write(spans))); } public static List decodeList(ByteBuffer spans) { Buffer buffer = new Buffer(); try { buffer.write(spans); } catch (IOException e) { throw new AssertionError(e); // no I/O } return decodeList(new ProtoReader(buffer)); } public static List decodeList(ProtoReader input) { ArrayList spans = new ArrayList<>(); final long token; try { token = input.beginMessage(); } catch (IOException e) { throw new UncheckedIOException(e); } try { boolean done = false; while (!done) { int tag = input.nextTag(); switch (tag) { case -1: done = true; break; case 1: { long subToken = input.beginMessage(); spans.add(decodeOne(input)); input.endMessageAndGetUnknownFields(subToken); break; } default: { logAndSkip(input, tag); break; } } } } catch (IOException e) { throw new RuntimeException(e); } try { input.endMessageAndGetUnknownFields(token); } catch (IOException e) { throw new UncheckedIOException(e); } return spans; } static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; // https://github.com/square/wire/issues/958 private static String readHexString(ProtoReader input) throws IOException { ByteString bytes = input.readBytes(); int length = bytes.size() * 2; // All our hex fields are at most 32 characters. if (length > 32) { throw new AssertionError("hex field greater than 32 chars long: " + length); } char[] result = RecyclableBuffers.shortStringBuffer(); for (int i = 0; i < bytes.size(); i++) { byte b = bytes.getByte(i); result[2 * i] = HEX_DIGITS[(b >> 4) & 0xf]; result[2 * i + 1] = HEX_DIGITS[b & 0xf]; } return new String(result, 0, length); } static void logAndSkip(ProtoReader input, int tag) throws IOException { if (DEBUG) { // avoiding volatile reads as we don't log on skip in our normal codec int nextWireType = WireFormat.getTagWireType(tag); int nextFieldNumber = WireFormat.getTagFieldNumber(tag); LOG.fine("Skipping field: byte=%s, fieldNumber=%s, wireType=%s".formatted( 0, nextFieldNumber, nextWireType)); } input.skip(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/collector/MetricsBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.collector; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import zipkin2.server.internal.MicrometerCollectorMetrics; @Measurement(iterations = 80, time = 1) @Warmup(iterations = 20, time = 1) @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(1) public class MetricsBenchmarks { static final int LONG_SPAN = 5000; static final int MEDIUM_SPAN = 1000; static final int SHORT_SPAN = 500; private MeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); private InMemoryCollectorMetrics inMemoryCollectorMetrics = new InMemoryCollectorMetrics() .forTransport("jmh"); private MicrometerCollectorMetrics micrometerCollectorMetrics = new MicrometerCollectorMetrics(registry) .forTransport("jmh"); @Benchmark public int incrementBytes_longSpans_inMemory() { return incrementBytes(inMemoryCollectorMetrics, LONG_SPAN); } @Benchmark public int incrementBytes_longSpans_Actuate() { return incrementBytes(micrometerCollectorMetrics, LONG_SPAN); } @Benchmark public int incrementBytes_mediumSpans_inMemory() { return incrementBytes(inMemoryCollectorMetrics, MEDIUM_SPAN); } @Benchmark public int incrementBytes_mediumSpans_Actuate() { return incrementBytes(micrometerCollectorMetrics, MEDIUM_SPAN); } @Benchmark public int incrementBytes_shortSpans_inMemory() { return incrementBytes(inMemoryCollectorMetrics, SHORT_SPAN); } @Benchmark public int incrementBytes_shortSpans_Actuate() { return incrementBytes(micrometerCollectorMetrics, SHORT_SPAN); } private int incrementBytes(CollectorMetrics collectorMetrics, int bytes) { collectorMetrics.incrementBytes(bytes); return bytes; } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + MetricsBenchmarks.class.getSimpleName() + ".*") .threads(40) .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/elasticsearch/internal/BulkRequestBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.elasticsearch.internal; import com.linecorp.armeria.common.HttpRequest; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import zipkin2.Span; import zipkin2.codec.SpanBytesDecoder; import zipkin2.elasticsearch.ElasticsearchStorage; import zipkin2.elasticsearch.internal.BulkCallBuilder.IndexEntry; import static java.nio.charset.StandardCharsets.UTF_8; import static zipkin2.elasticsearch.ElasticsearchVersion.V6_0; import static zipkin2.storage.cassandra.internal.Resources.resourceToString; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(2) public class BulkRequestBenchmarks { static final Span CLIENT_SPAN = SpanBytesDecoder.JSON_V2.decodeOne(resourceToString("/zipkin2-client.json").getBytes(UTF_8)); final ElasticsearchStorage es = ElasticsearchStorage.newBuilder(() -> null).build(); final long indexTimestamp = CLIENT_SPAN.timestampAsLong() / 1000L; final String spanIndex = es.indexNameFormatter().formatTypeAndTimestampForInsert("span", '-', indexTimestamp); final IndexEntry entry = BulkCallBuilder.newIndexEntry(spanIndex, "span", CLIENT_SPAN, BulkIndexWriter.SPAN); @Benchmark public ByteBuf writeRequest_singleSpan() { return BulkCallBuilder.serialize(PooledByteBufAllocator.DEFAULT, entry, true); } @Benchmark public HttpRequest buildAndWriteRequest_singleSpan() { BulkCallBuilder builder = new BulkCallBuilder(es, V6_0, "index-span"); builder.index(spanIndex, "span", CLIENT_SPAN, BulkIndexWriter.SPAN); return builder.build().request.get(); } @Benchmark public HttpRequest buildAndWriteRequest_tenSpans() { BulkCallBuilder builder = new BulkCallBuilder(es, V6_0, "index-span"); for (int i = 0; i < 10; i++) { builder.index(spanIndex, "span", CLIENT_SPAN, BulkIndexWriter.SPAN); } return builder.build().request.get(); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .addProfiler("gc") .include(".*" + BulkRequestBenchmarks.class.getSimpleName() + ".*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/internal/DelayLimiterBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.internal; import java.util.Random; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(2) public class DelayLimiterBenchmarks { final Random rng = new Random(); final DelayLimiter limiter = DelayLimiter.newBuilder() .ttl(1L, TimeUnit.HOURS) // legacy default from Cassandra .cardinality(5 * 4000) // Ex. 5 site tags with cardinality 4000 each .build(); @Benchmark public boolean shouldInvoke_randomData() { return limiter.shouldInvoke(rng.nextLong()); } @Benchmark public boolean shouldInvoke_sameData() { return limiter.shouldInvoke(1L); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .addProfiler("gc") .include(".*" + DelayLimiterBenchmarks.class.getSimpleName() + ".*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/internal/Proto3CodecInteropTest.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.internal; import com.squareup.wire.ProtoWriter; import java.io.IOException; import java.util.List; import java.util.Map; import okio.ByteString; import org.assertj.core.data.MapEntry; import org.junit.jupiter.api.Test; import zipkin2.codec.SpanBytesDecoder; import zipkin2.codec.SpanBytesEncoder; import zipkin2.internal.Proto3ZipkinFields.TagField; import zipkin2.proto3.Annotation; import zipkin2.proto3.Endpoint; import zipkin2.proto3.ListOfSpans; import zipkin2.proto3.Span; import static okio.ByteString.decodeHex; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.MapEntry.entry; import static zipkin2.internal.Proto3ZipkinFields.SPAN; import static zipkin2.internal.Proto3ZipkinFields.SpanField.ANNOTATION; import static zipkin2.internal.Proto3ZipkinFields.SpanField.LOCAL_ENDPOINT; import static zipkin2.internal.Proto3ZipkinFields.SpanField.REMOTE_ENDPOINT; import static zipkin2.internal.Proto3ZipkinFields.SpanField.TAG_KEY; // Compares against Square Wire as it is easier than Google's protobuf tooling public class Proto3CodecInteropTest { static final zipkin2.Endpoint ORDER = zipkin2.Endpoint.newBuilder() .serviceName("订单维护服务") .ip("2001:db8::c001") .build(); static final zipkin2.Endpoint PROFILE = zipkin2.Endpoint.newBuilder() .serviceName("个人信息服务") .ip("192.168.99.101") .port(9000) .build(); static final zipkin2.Span ZIPKIN_SPAN = zipkin2.Span.newBuilder() .traceId("4d1e00c0db9010db86154a4ba6e91385") .parentId("86154a4ba6e91385") .id("4d1e00c0db9010db") .kind(zipkin2.Span.Kind.SERVER) .name("个人信息查询") .timestamp(1472470996199000L) .duration(207000L) .localEndpoint(ORDER) .remoteEndpoint(PROFILE) .addAnnotation(1472470996199000L, "foo happened") .putTag("http.path", "/person/profile/query") .putTag("http.status_code", "403") .putTag("clnt/finagle.version", "6.45.0") .putTag("error", "此用户没有操作权限") .shared(true) .build(); static final List ZIPKIN_SPANS = List.of(ZIPKIN_SPAN, ZIPKIN_SPAN); static final Span PROTO_SPAN = new Span.Builder() .trace_id(decodeHex(ZIPKIN_SPAN.traceId())) .parent_id(decodeHex(ZIPKIN_SPAN.parentId())) .id(decodeHex(ZIPKIN_SPAN.id())) .kind(Span.Kind.valueOf(ZIPKIN_SPAN.kind().name())) .name(ZIPKIN_SPAN.name()) .timestamp(ZIPKIN_SPAN.timestampAsLong()) .duration(ZIPKIN_SPAN.durationAsLong()) .local_endpoint(new Endpoint.Builder() .service_name(ORDER.serviceName()) .ipv6(ByteString.of(ORDER.ipv6Bytes())).build() ) .remote_endpoint(new Endpoint.Builder() .service_name(PROFILE.serviceName()) .ipv4(ByteString.of(PROFILE.ipv4Bytes())) .port(PROFILE.portAsInt()).build() ) .annotations(List.of(new Annotation.Builder() .timestamp(ZIPKIN_SPAN.annotations().get(0).timestamp()) .value(ZIPKIN_SPAN.annotations().get(0).value()) .build())) .tags(ZIPKIN_SPAN.tags()) .shared(true) .build(); ListOfSpans PROTO_SPANS = new ListOfSpans.Builder() .spans(List.of(PROTO_SPAN, PROTO_SPAN)).build(); @Test void encodeIsCompatible() throws IOException { okio.Buffer buffer = new okio.Buffer(); Span.ADAPTER.encodeWithTag(new ProtoWriter(buffer), 1, PROTO_SPAN); assertThat(SpanBytesEncoder.PROTO3.encode(ZIPKIN_SPAN)) .containsExactly(buffer.readByteArray()); } @Test void decodeOneIsCompatible() { assertThat(SpanBytesDecoder.PROTO3.decodeOne(PROTO_SPANS.encode())) .isEqualTo(ZIPKIN_SPAN); } @Test void decodeListIsCompatible() { assertThat(SpanBytesDecoder.PROTO3.decodeList(PROTO_SPANS.encode())) .containsExactly(ZIPKIN_SPAN, ZIPKIN_SPAN); } @Test void encodeListIsCompatible_buff() { byte[] wireBytes = PROTO_SPANS.encode(); byte[] zipkin_buff = new byte[10 + wireBytes.length]; assertThat(SpanBytesEncoder.PROTO3.encodeList(ZIPKIN_SPANS, zipkin_buff, 5)) .isEqualTo(wireBytes.length); assertThat(zipkin_buff) .startsWith(0, 0, 0, 0, 0) .containsSequence(wireBytes) .endsWith(0, 0, 0, 0, 0); } @Test void encodeListIsCompatible() { byte[] wireBytes = PROTO_SPANS.encode(); assertThat(SpanBytesEncoder.PROTO3.encodeList(ZIPKIN_SPANS)) .containsExactly(wireBytes); } @Test void span_sizeInBytes_matchesWire() { assertThat(SPAN.sizeInBytes(ZIPKIN_SPAN)) .isEqualTo(Span.ADAPTER.encodedSizeWithTag(SPAN.fieldNumber, PROTO_SPAN)); } @Test void annotation_sizeInBytes_matchesWire() { zipkin2.Annotation zipkinAnnotation = ZIPKIN_SPAN.annotations().get(0); assertThat(ANNOTATION.sizeInBytes(zipkinAnnotation)).isEqualTo( Annotation.ADAPTER.encodedSizeWithTag(ANNOTATION.fieldNumber, PROTO_SPAN.annotations.get(0)) ); } @Test void annotation_write_matchesWire() { zipkin2.Annotation zipkinAnnotation = ZIPKIN_SPAN.annotations().get(0); Span wireSpan = new Span.Builder().annotations(PROTO_SPAN.annotations).build(); byte[] zipkinBytes = new byte[ANNOTATION.sizeInBytes(zipkinAnnotation)]; ANNOTATION.write(WriteBuffer.wrap(zipkinBytes, 0), zipkinAnnotation); assertThat(zipkinBytes) .containsExactly(wireSpan.encode()); } @Test void annotation_read_matchesWireEncodingWithTag() { zipkin2.Annotation zipkinAnnotation = ZIPKIN_SPAN.annotations().get(0); Span wireSpan = new Span.Builder().annotations(PROTO_SPAN.annotations).build(); ReadBuffer wireBytes = ReadBuffer.wrap(wireSpan.encode()); assertThat(wireBytes.readVarint32()) .isEqualTo(ANNOTATION.key); zipkin2.Span.Builder builder = zipkinSpanBuilder(); ANNOTATION.readLengthPrefixAndValue(wireBytes, builder); assertThat(builder.build().annotations()) .containsExactly(zipkinAnnotation); } @Test void endpoint_sizeInBytes_matchesWireEncodingWithTag() { assertThat(LOCAL_ENDPOINT.sizeInBytes(ZIPKIN_SPAN.localEndpoint())).isEqualTo( Endpoint.ADAPTER.encodedSizeWithTag(LOCAL_ENDPOINT.fieldNumber, PROTO_SPAN.local_endpoint) ); assertThat(REMOTE_ENDPOINT.sizeInBytes(ZIPKIN_SPAN.remoteEndpoint())).isEqualTo( Endpoint.ADAPTER.encodedSizeWithTag(REMOTE_ENDPOINT.fieldNumber, PROTO_SPAN.remote_endpoint) ); } @Test void localEndpoint_write_matchesWire() { byte[] zipkinBytes = new byte[LOCAL_ENDPOINT.sizeInBytes(ZIPKIN_SPAN.localEndpoint())]; LOCAL_ENDPOINT.write(WriteBuffer.wrap(zipkinBytes, 0), ZIPKIN_SPAN.localEndpoint()); Span wireSpan = new Span.Builder().local_endpoint(PROTO_SPAN.local_endpoint).build(); assertThat(zipkinBytes) .containsExactly(wireSpan.encode()); } @Test void remoteEndpoint_write_matchesWire() { byte[] zipkinBytes = new byte[REMOTE_ENDPOINT.sizeInBytes(ZIPKIN_SPAN.remoteEndpoint())]; REMOTE_ENDPOINT.write(WriteBuffer.wrap(zipkinBytes, 0), ZIPKIN_SPAN.remoteEndpoint()); Span wireSpan = new Span.Builder().remote_endpoint(PROTO_SPAN.remote_endpoint).build(); assertThat(zipkinBytes) .containsExactly(wireSpan.encode()); } @Test void tag_sizeInBytes_matchesWire() { MapEntry entry = entry("clnt/finagle.version", "6.45.0"); Span wireSpan = new Span.Builder().tags(Map.of(entry.key, entry.value)).build(); assertThat(new TagField(TAG_KEY).sizeInBytes(entry)) .isEqualTo(Span.ADAPTER.encodedSize(wireSpan)); } @Test void writeTagField_matchesWire() { MapEntry entry = entry("clnt/finagle.version", "6.45.0"); TagField field = new TagField(TAG_KEY); byte[] zipkinBytes = new byte[field.sizeInBytes(entry)]; field.write(WriteBuffer.wrap(zipkinBytes, 0), entry); Span oneField = new Span.Builder().tags(Map.of(entry.key, entry.value)).build(); assertThat(zipkinBytes) .containsExactly(oneField.encode()); } @Test void writeTagField_matchesWire_emptyValue() { MapEntry entry = entry("error", ""); TagField field = new TagField(TAG_KEY); byte[] zipkinBytes = new byte[field.sizeInBytes(entry)]; field.write(WriteBuffer.wrap(zipkinBytes, 0), entry); Span oneField = new Span.Builder().tags(Map.of(entry.key, entry.value)).build(); assertThat(zipkinBytes) .containsExactly(oneField.encode()); } static zipkin2.Span.Builder zipkinSpanBuilder() { return zipkin2.Span.newBuilder().traceId("a").id("a"); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/internal/ReadBufferBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.internal; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(1) public class ReadBufferBenchmarks { byte[] longBuff = { (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, }; @Benchmark public long readLong() { int pos = 0; return (longBuff[pos] & 0xffL) << 56 | (longBuff[pos + 1] & 0xffL) << 48 | (longBuff[pos + 2] & 0xffL) << 40 | (longBuff[pos + 3] & 0xffL) << 32 | (longBuff[pos + 4] & 0xffL) << 24 | (longBuff[pos + 5] & 0xffL) << 16 | (longBuff[pos + 6] & 0xffL) << 8 | (longBuff[pos + 7] & 0xffL); } @Benchmark public long readLong_localArray() { int pos = 0; byte[] longBuff = this.longBuff; return (longBuff[pos] & 0xffL) << 56 | (longBuff[pos + 1] & 0xffL) << 48 | (longBuff[pos + 2] & 0xffL) << 40 | (longBuff[pos + 3] & 0xffL) << 32 | (longBuff[pos + 4] & 0xffL) << 24 | (longBuff[pos + 5] & 0xffL) << 16 | (longBuff[pos + 6] & 0xffL) << 8 | (longBuff[pos + 7] & 0xffL); } @Benchmark public long readLong_8arity_localArray() { int pos = 0; return readLong( longBuff[pos] & 0xff, longBuff[pos + 1] & 0xff, longBuff[pos + 2] & 0xff, longBuff[pos + 3] & 0xff, longBuff[pos + 4] & 0xff, longBuff[pos + 5] & 0xff, longBuff[pos + 6] & 0xff, longBuff[pos + 7] & 0xff ); } @Benchmark public long readLong_8arity() { int pos = 0; byte[] longBuff = this.longBuff; return readLong( longBuff[pos] & 0xff, longBuff[pos + 1] & 0xff, longBuff[pos + 2] & 0xff, longBuff[pos + 3] & 0xff, longBuff[pos + 4] & 0xff, longBuff[pos + 5] & 0xff, longBuff[pos + 6] & 0xff, longBuff[pos + 7] & 0xff ); } static long readLong(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7) { return (p0 & 0xffL) << 56 | (p1 & 0xffL) << 48 | (p2 & 0xffL) << 40 | (p3 & 0xffL) << 32 | (p4 & 0xffL) << 24 | (p5 & 0xffL) << 16 | (p6 & 0xffL) << 8 | (p7 & 0xffL); } @Benchmark public long readLongReverseBytes() { return Long.reverseBytes(readLong()); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + ReadBufferBenchmarks.class.getSimpleName() + ".*") .addProfiler("gc") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/internal/WriteBufferBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.internal; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import static java.nio.charset.StandardCharsets.UTF_8; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(1) public class WriteBufferBenchmarks { // Order id = d07c4daa-0fa9-4c03-90b1-e06c4edae250 doesn't exist static final String CHINESE_UTF8 = "订单d07c4daa-0fa9-4c03-90b1-e06c4edae250不存在"; static final int CHINESE_UTF8_SIZE = UTF_8.encode(CHINESE_UTF8).remaining(); /* length-prefixing a 1 KiB span */ static final int TEST_INT = 1024; /* epoch micros timestamp */ static final long TEST_LONG = 1472470996199000L; byte[] bytes = new byte[8]; WriteBuffer buffer = WriteBuffer.wrap(bytes); @Benchmark public int utf8SizeInBytes_chinese() { return WriteBuffer.utf8SizeInBytes(CHINESE_UTF8); } @Benchmark public byte[] writeUtf8_chinese() { byte[] bytesUtf8 = new byte[CHINESE_UTF8_SIZE]; WriteBuffer.wrap(bytesUtf8, 0).writeUtf8(CHINESE_UTF8); return bytesUtf8; } @Benchmark public ByteBuffer writeUtf8_chinese_jdk() { return UTF_8.encode(CHINESE_UTF8); } @Benchmark public int varIntSizeInBytes_32() { return WriteBuffer.varintSizeInBytes(TEST_INT); } @Benchmark public int varIntSizeInBytes_64() { return WriteBuffer.varintSizeInBytes(TEST_LONG); } @Benchmark public int writeVarint_32() { buffer.writeVarint(TEST_INT); return buffer.pos(); } @Benchmark public int writeVarint_64() { buffer.writeVarint(TEST_LONG); return buffer.pos(); } @Benchmark public int writeLongLe() { buffer.writeLongLe(TEST_LONG); return buffer.pos(); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + WriteBufferBenchmarks.class.getSimpleName() + ".*") .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/server/ServerIntegratedBenchmark.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.server; import com.fasterxml.jackson.databind.ObjectMapper; import com.linecorp.armeria.client.WebClient; import io.netty.handler.codec.http.QueryStringEncoder; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.lifecycle.Startables; import org.testcontainers.utility.MountableFile; import static org.testcontainers.utility.DockerImageName.parse; /** * This benchmark runs zipkin-server, storage backends, an example application, prometheus, grafana, * and wrk using docker to generate a performance report. * *

Currently there are two environment variable knobs * *

    *
  • * RELEASE_VERSION - specify to a released zipkin server. If unspecified, will use the current code, * i.e., the code currently displayed in your IDE. *
  • *
  • * ZIPKIN_BENCHMARK_WAIT - set to true to have the benchmark wait until user manually terminates at the end. * Useful to manually inspect prometheus / grafana. *
  • *
* *

Note to Windows laptop users: you will probably need to restart the Docker daemon before a * session of benchmarks. Docker containers seem to have time get out of sync when a computer sleeps * until you restart the daemon - this causes Prometheus metrics to not scrape properly. */ @Disabled // Run manually class ServerIntegratedBenchmark { static final Logger LOG = LoggerFactory.getLogger(ServerIntegratedBenchmark.class); static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Nullable static final String RELEASE_VERSION = System.getenv("RELEASE_VERSION"); static final boolean WAIT_AFTER_BENCHMARK = "true".equals(System.getenv("ZIPKIN_BENCHMARK_WAIT")); List> containers; @BeforeEach void setUp() { containers = new ArrayList<>(); } @AfterEach void tearDown() { containers.forEach(GenericContainer::stop); } @Test void inMemory() throws Exception { runBenchmark(null); } @Test void elasticsearch() throws Exception { GenericContainer elasticsearch = new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-elasticsearch7:3.4.3")) .withNetwork(Network.SHARED) .withNetworkAliases("elasticsearch") .withLabel("name", "elasticsearch") .withLabel("storageType", "elasticsearch") .withExposedPorts(9200) .waitingFor(Wait.forHealthcheck()); containers.add(elasticsearch); runBenchmark(elasticsearch); } @Test void cassandra3() throws Exception { GenericContainer cassandra = new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-cassandra:3.4.3")) .withNetwork(Network.SHARED) .withNetworkAliases("cassandra") .withLabel("name", "cassandra") .withLabel("storageType", "cassandra3") .withExposedPorts(9042) .waitingFor(Wait.forHealthcheck()); containers.add(cassandra); runBenchmark(cassandra); } @Test void mysql() throws Exception { GenericContainer mysql = new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin-mysql:3.4.3")) .withNetwork(Network.SHARED) .withNetworkAliases("mysql") .withLabel("name", "mysql") .withLabel("storageType", "mysql") .withExposedPorts(3306) .waitingFor(Wait.forHealthcheck()); containers.add(mysql); runBenchmark(mysql); } void runBenchmark(@Nullable GenericContainer storage) throws Exception { runBenchmark(storage, createZipkinContainer(storage)); } void runBenchmark(@Nullable GenericContainer storage, GenericContainer zipkin) throws Exception { GenericContainer backend = new GenericContainer<>(parse("ghcr.io/openzipkin/brave-example:armeria")) .withNetwork(Network.SHARED) .withNetworkAliases("backend") .withCommand("backend") .withExposedPorts(9000) .waitingFor(Wait.forHealthcheck()); GenericContainer frontend = new GenericContainer<>(parse("ghcr.io/openzipkin/brave-example:armeria")) .withNetwork(Network.SHARED) .withNetworkAliases("frontend") .withCommand("frontend") .withExposedPorts(8081) .waitingFor(Wait.forHealthcheck()); containers.add(frontend); // Use a quay.io mirror to prevent build outages due to Docker Hub pull quotas // Use same version as in docker/examples/docker-compose-prometheus.yml GenericContainer prometheus = new GenericContainer<>(parse("quay.io/prometheus/prometheus:v2.55.1")) .withNetwork(Network.SHARED) .withNetworkAliases("prometheus") .withExposedPorts(9090) .withCopyFileToContainer( MountableFile.forClasspathResource("prometheus.yml"), "/etc/prometheus/prometheus.yml"); containers.add(prometheus); // Use a quay.io mirror to prevent build outages due to Docker Hub pull quotas // Use same version as in docker/examples/docker-compose-prometheus.yml GenericContainer grafana = new GenericContainer<>(parse("quay.io/giantswarm/grafana:7.5.12")) .withNetwork(Network.SHARED) .withNetworkAliases("grafana") .withExposedPorts(3000) .withEnv("GF_AUTH_ANONYMOUS_ENABLED", "true") .withEnv("GF_AUTH_ANONYMOUS_ORG_ROLE", "Admin"); containers.add(grafana); // This is an arbitrary small image that has curl installed // Use a quay.io mirror to prevent build outages due to Docker Hub pull quotas // Use same version as in docker/examples/docker-compose-prometheus.yml GenericContainer grafanaDashboards = new GenericContainer<>(parse("quay.io/cilium/alpine-curl:v1.10.0")) .withNetwork(Network.SHARED) .withWorkingDirectory("/tmp") .withLogConsumer(new Slf4jLogConsumer(LOG)) .withCreateContainerCmdModifier(it -> it.withEntrypoint("/tmp/create.sh")) .withCopyFileToContainer( MountableFile.forClasspathResource("create-datasource-and-dashboard.sh", 555), "/tmp/create.sh"); containers.add(grafanaDashboards); // Use a quay.io mirror to prevent build outages due to Docker Hub pull quotas GenericContainer wrk = new GenericContainer<>(parse("quay.io/dim/wrk:stable")) .withNetwork(Network.SHARED) .withCreateContainerCmdModifier(it -> it.withEntrypoint("wrk")) .withCommand("-t4 -c128 -d100s http://frontend:8081 --latency"); containers.add(wrk); grafanaDashboards.dependsOn(grafana); wrk.dependsOn(frontend, backend, prometheus, grafanaDashboards, zipkin); if (storage != null) wrk.dependsOn(storage); Startables.deepStart(Stream.of(wrk)).join(); System.out.println("Benchmark started."); if (zipkin != null) printContainerMapping(zipkin); if (storage != null) printContainerMapping(storage); printContainerMapping(backend); printContainerMapping(frontend); printContainerMapping(prometheus); printContainerMapping(grafana); while (wrk.isRunning()) { Thread.sleep(1000); } // Wait for prometheus to do a final scrape. Thread.sleep(5000); System.out.println("Benchmark complete, wrk output:"); System.out.println(wrk.getLogs().replace("\n\n", "\n")); WebClient prometheusClient = WebClient.of( "h1c://" + prometheus.getContainerIpAddress() + ":" + prometheus.getFirstMappedPort()); System.out.printf("Messages received: %s%n", prometheusValue( prometheusClient, "sum(zipkin_collector_messages_total)")); System.out.printf("Spans received: %s%n", prometheusValue( prometheusClient, "sum(zipkin_collector_spans_total)")); System.out.printf("Spans dropped: %s%n", prometheusValue( prometheusClient, "sum(zipkin_collector_spans_dropped_total)")); System.out.println("Memory quantiles:"); printQuartiles(prometheusClient, "jvm_memory_used_bytes{area=\"heap\"}"); printQuartiles(prometheusClient, "jvm_memory_used_bytes{area=\"nonheap\"}"); System.out.printf( "Total GC time (s): %s%n", prometheusValue(prometheusClient, "sum(jvm_gc_pause_seconds_sum)")); System.out.printf( "Number of GCs: %s%n", prometheusValue(prometheusClient, "sum(jvm_gc_pause_seconds_count)")); System.out.println("POST Spans latency (s)"); printHistogram(prometheusClient, """ http_server_requests_seconds_bucket{ method="POST",status="202",uri="/api/v2/spans"} """); if (WAIT_AFTER_BENCHMARK) { System.out.println(""" Keeping containers running until explicit termination. \ Feel free to poke around in grafana.\ """); Thread.sleep(Long.MAX_VALUE); } } GenericContainer createZipkinContainer(@Nullable GenericContainer storage) throws Exception { Map env = new LinkedHashMap<>(); if (storage != null) { String name = storage.getLabels().get("name"); String host = name; int port = storage.getExposedPorts().get(0); String address = host + ":" + port; env.put("STORAGE_TYPE", storage.getLabels().get("storageType")); switch (name) { case "elasticsearch": env.put("ES_HOSTS", "http://" + address); break; case "cassandra": case "cassandra3": env.put("CASSANDRA_CONTACT_POINTS", address); break; case "mysql": env.put("MYSQL_HOST", host); env.put("MYSQL_TCP_PORT", Integer.toString(port)); env.put("MYSQL_USER", "zipkin"); env.put("MYSQL_PASS", "zipkin"); break; default: throw new IllegalArgumentException("Unknown storage " + name + ". Update startZipkin to map it to properties."); } } final GenericContainer zipkin; if (RELEASE_VERSION == null) { zipkin = new GenericContainer<>(parse("ghcr.io/openzipkin/java:21.0.6_p7")); List classpath = new ArrayList<>(); for (String item : System.getProperty("java.class.path").split(File.pathSeparator)) { Path path = Paths.get(item); final String containerPath; if (Files.isDirectory(path)) { Path root = path.getParent(); while (root != null) { try (Stream f = Files.list(root)) { if (f.anyMatch(p -> p.getFileName().toString().equals("mvnw"))) { break; } } root = root.getParent(); } containerPath = root.relativize(path).toString().replace('\\', '/'); } else { containerPath = path.getFileName().toString(); } // Test containers currently doesn't support copying in a path with subdirectories that // need to be created, so we mangle directory structure into a single directory with // hyphens. String classPathItem = "/classpath-" + containerPath.replace('/', '-'); zipkin.withCopyFileToContainer(MountableFile.forHostPath(item), classPathItem); classpath.add(classPathItem); } zipkin.withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint("java")); zipkin.setCommand("-cp", String.join(":", classpath), "zipkin.server.ZipkinServer"); // Don't fail on classpath problem from missing lens, as we don't use it. env.put("ZIPKIN_UI_ENABLED", "false"); } else { zipkin = new GenericContainer<>(parse("ghcr.io/openzipkin/zipkin:" + RELEASE_VERSION)); } zipkin .withNetwork(Network.SHARED) .withNetworkAliases("zipkin") .withExposedPorts(9411) .withEnv(env) .waitingFor(Wait.forHttp("/health")); containers.add(zipkin); return zipkin; } static void printContainerMapping(GenericContainer container) { System.out.printf( "Container %s ports exposed at %s%n", container.getDockerImageName(), container.getExposedPorts().stream() .map(port -> Map.entry(port, "http://" + container.getContainerIpAddress() + ":" + container.getMappedPort(port))) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); } static void printQuartiles(WebClient prometheus, String metric) throws Exception { for (double quantile : Arrays.asList(0.0, 0.25, 0.5, 0.75, 1.0)) { String value = prometheusValue(prometheus, "quantile(" + quantile + ", " + metric + ")"); System.out.printf("%s[%s] = %s%n", metric, quantile, value); } } static void printHistogram(WebClient prometheus, String metric) throws Exception { for (double quantile : Arrays.asList(0.5, 0.9, 0.99)) { String value = prometheusValue(prometheus, "histogram_quantile(" + quantile + ", " + metric + ")"); System.out.printf("%s[%s] = %s%n", metric, quantile, value); } } static String prometheusValue(WebClient prometheus, String query) throws Exception { QueryStringEncoder encoder = new QueryStringEncoder("/api/v1/query"); encoder.addParam("query", query); String response = prometheus.get(encoder.toString()).aggregate().join().contentUtf8(); return OBJECT_MAPPER.readTree(response).at("/data/result/0/value/1").asText(); } } ================================================ FILE: benchmarks/src/test/java/zipkin2/server/internal/throttle/ThrottledCallBenchmarks.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2.server.internal.throttle; import com.linecorp.armeria.common.metric.NoopMeterRegistry; import com.netflix.concurrency.limits.limit.FixedLimit; import com.netflix.concurrency.limits.limiter.SimpleLimiter; import java.io.IOException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import zipkin2.Call; import zipkin2.Callback; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(2) public class ThrottledCallBenchmarks { ExecutorService fakeCallExecutor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor(); ThrottledCall call; @Setup public void setup() { executor = Executors.newSingleThreadExecutor(); fakeCallExecutor = Executors.newSingleThreadExecutor(); SimpleLimiter limiter = SimpleLimiter.newBuilder().limit(FixedLimit.of(1)).build(); LimiterMetrics metrics = new LimiterMetrics(NoopMeterRegistry.get()); Predicate isOverCapacity = RejectedExecutionException.class::isInstance; call = new ThrottledCall(new FakeCall(fakeCallExecutor), executor, limiter, metrics, isOverCapacity); } @TearDown public void tearDown() { executor.shutdown(); fakeCallExecutor.shutdown(); } @Benchmark public Object execute() throws IOException { return call.clone().execute(); } @Benchmark public void execute_overCapacity() throws IOException { ThrottledCall overCapacity = (ThrottledCall) call.clone(); ((FakeCall) overCapacity.delegate).overCapacity = true; try { overCapacity.execute(); } catch (RejectedExecutionException e) { assert e == OVER_CAPACITY; } } @Benchmark public void execute_throttled() throws IOException { call.limiter.acquire(null); // capacity is 1, so this will overdo it. call.clone().execute(); } static final RejectedExecutionException OVER_CAPACITY = new RejectedExecutionException(); static final class FakeCall extends Call.Base { final Executor executor; boolean overCapacity = false; FakeCall(Executor executor) { this.executor = executor; } @Override public Void doExecute() throws IOException { if (overCapacity) throw OVER_CAPACITY; return null; } @Override public void doEnqueue(Callback callback) { executor.execute(() -> { if (overCapacity) { callback.onError(OVER_CAPACITY); } else { callback.onSuccess(null); } }); } @Override public FakeCall clone() { return new FakeCall(executor); } } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .addProfiler("gc") .include(".*" + ThrottledCallBenchmarks.class.getSimpleName()) .build(); new Runner(opt).run(); } } ================================================ FILE: benchmarks/src/test/resources/create-datasource-and-dashboard.sh ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -xeuo pipefail if ! curl --retry 5 --retry-connrefused --retry-delay 0 -sf http://grafana:3000/api/datasources/name/prom; then curl -sf -X POST -H "Content-Type: application/json" \ --data-binary '{"name":"prom","type":"prometheus","url":"http://prometheus:9090","access":"proxy","isDefault":true}' \ http://grafana:3000/api/datasources fi dashboard_id=1598 last_revision=$(curl -sf https://grafana.com/api/dashboards/${dashboard_id}/revisions | grep '"revision":' | sed 's/ *"revision": \([0-9]*\),/\1/' | sort -n | tail -1) echo '{"dashboard": ' > data.json curl -s https://grafana.com/api/dashboards/${dashboard_id}/revisions/${last_revision}/download >> data.json echo ', "inputs": [{"name": "DS_PROMETHEUS", "pluginId": "prometheus", "type": "datasource", "value": "prom"}], "overwrite": false}' >> data.json curl --retry-connrefused --retry 5 --retry-delay 0 -sf \ -X POST -H "Content-Type: application/json" \ --data-binary @data.json \ http://grafana:3000/api/dashboards/import ================================================ FILE: benchmarks/src/test/resources/prometheus.yml ================================================ global: scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - job_name: 'zipkin' scrape_interval: 5s metrics_path: '/prometheus' static_configs: - targets: ['zipkin:9411'] ================================================ FILE: benchmarks/src/test/resources/simplelogger.properties ================================================ # See https://www.slf4j.org/api/org/slf4j/impl/SimpleLogger.html for the full list of config options org.slf4j.simpleLogger.logFile=System.out org.slf4j.simpleLogger.defaultLogLevel=warn org.slf4j.simpleLogger.showDateTime=true org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS # uncomment to include kafka consumer configuration in test logs #logger.org.apache.kafka.clients.level=info ================================================ FILE: benchmarks/src/test/resources/zipkin2-chinese.json ================================================ { "traceId": "4d1e00c0db9010db86154a4ba6e91385", "parentId": "86154a4ba6e91385", "id": "4d1e00c0db9010db", "kind": "CLIENT", "name": "个人信息查询", "timestamp": 1472470996199000, "duration": 207000, "localEndpoint": { "serviceName": "订单维护服务", "ipv6": "2001:db8::c001" }, "remoteEndpoint": { "serviceName": "个人信息服务", "ipv4": "192.168.99.101", "port": 9000 }, "tags": { "http.path": "/person/profile/query", "http.status_code": "403", "error": "此用户没有操作权限" } } ================================================ FILE: benchmarks/src/test/resources/zipkin2-client.json ================================================ { "traceId": "4d1e00c0db9010db86154a4ba6e91385", "parentId": "86154a4ba6e91385", "id": "4d1e00c0db9010db", "kind": "CLIENT", "name": "get", "timestamp": 1472470996199000, "duration": 207000, "localEndpoint": { "serviceName": "frontend", "ipv6": "2001:db8::c001" }, "remoteEndpoint": { "serviceName": "backend", "ipv4": "192.168.99.101", "port": 9000 }, "annotations": [ { "timestamp": 1472470996238000, "value": "foo" }, { "timestamp": 1472470996403000, "value": "bar" } ], "tags": { "http.path": "/api", "clnt/finagle.version": "6.45.0" } } ================================================ FILE: build-bin/README.md ================================================ # Test and Deploy scripts This is a Maven+Docker project, which uses standard conventions for test and deploy with some exceptions. On [../zipkin-lens]: * [maybe_install_npm] is used to build on an unsupported node.js architecture. * [maven_go_offline] additionally seeds the NPM cache On test: * [test], used by [../.github/workflows/test.yml] runs Maven unit and integration tests. * Its "test" job skips docker, as they are run in parallel in "test_docker" * [../.github/workflows/readme_test.yml] tests build commands in [../zipkin-server] and [../docker] * zipkin, zipkin-lens and zipkin-slim Docker builds use `RELEASE_FROM_MAVEN_BUILD=true` * this avoids invoking Maven builds from within Docker, which are costly and fragile * Docker tests run in sequence to avoid queueing delays, which take longer than builds themselves. On deploy: * [deploy], used by [../.github/workflows/deploy.yml] publishes jars and Docker images. * [javadoc_to_gh_pages] pushes Javadoc to the gh-pages branch on MAJOR.MINOR.PATCH branch, but not master. * gh-pages is addressable via https://zipkin.io/zipkin/ * Besides production Docker images, this project includes [../docker/test-images]. * [docker_push] pushes test-images, but only to ghcr.io [//]: # (Below here should be standard for all projects) ## Build Overview `build-bin` holds portable scripts used in CI to test and deploy the project. The scripts here are portable. They do not include any CI provider-specific logic or ENV variables. This helps `test.yml` (GitHub Actions) and alternatives contain nearly the same contents, even if certain OpenZipkin projects need slight adjustments here. Portability has proven necessary, as OpenZipkin has had to transition CI providers many times due to feature and quota constraints. These scripts serve a second purpose, which is to facilitate manual releases, which has also happened many times due usually to service outages of CI providers. While tempting to use CI-provider specific tools, doing so can easily create a dependency where no one knows how to release anymore. Do not use provider-specific mechanisms to implement release flow. Instead, automate triggering of the scripts here. The only scripts that should be modified per project are in the base directory. Those in subdirectories, such as [docker](docker), should not vary project to project except accident of version drift. Intentional changes in subdirectories should be relevant and tested on multiple projects to ensure they can be blindly copy/pasted. Conversely, the files in the base directory are project specific entry-points for test and deploy actions and are entirely appropriate to vary per project. Here's an overview: ## Lint Lint makes sure that documentation and workflows are in-tact. CI providers should be configured to run lint on pull requests or pushes to the master branch, notably when the tag is blank. Linters should only run on documentation-only commits or those who affect workflow files. Linters must not depend on authenticated resources, as running lint can leak credentials. * [configure_lint](configure_lint) - Ensures linters are installed * [lint](lint) - Runs the linters We minimally check the following: * [markdown-link-check](https://github.com/tcort/markdown-link-check) on our Markdown content. * we maintain [mlc_config.json](mlc_config.json) for exceptions * [yamllint](https://github.com/adrienverge/yamllint) on our GitHub Actions Workflow YAML. * occasionally need line length exceptions via `# yamllint disable-line rule:line-length` ### Example GitHub Actions setup A simplest GitHub Actions `lint.yml` runs linters after configuring them, but only on relevant event conditions. The name `lint.yml` and job `lint` allows easy references to status badges and parity of the scripts it uses. The `on:` section obviates job creation and resource usage for irrelevant events. Notably, GitHub Actions includes the ability to skip documentation-only jobs. Here's a partial `lint.yml` including only the aspects mentioned above. ```yaml --- on: # yamllint disable-line rule:truthy push: # non-tagged pushes to master branches: - master tags-ignore: - '*' paths: - '**/*.md' - '.github/workflows/*.yml' - './build-bin/*lint' - ./build-bin/mlc_config.json pull_request: # pull requests targeted at the master branch. branches: - master paths: - '**/*.md' - '.github/workflows/*.yml' - './build-bin/*lint' - ./build-bin/mlc_config.json jobs: lint: name: Lint runs-on: ubuntu-24.04 # newest available distribution, aka noble # skip commits made by the release plugin if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Lint run: | build-bin/configure_lint build-bin/lint ``` ## Test Test builds and runs any tests of the project, including integration tests. CI providers should be configured to run tests on pull requests or pushes to the master branch, notably when the tag is blank. Tests should not run on documentation-only commits. Tests must not depend on authenticated resources, as running tests can leak credentials. * [configure_test](configure_test) - Sets up build environment for tests. * [test](test) - Builds and runs tests for this project. ### Example GitHub Actions setup A simplest GitHub Actions `test.yml` runs tests after configuring them, but only on relevant event conditions. The name `test.yml` and job `test` allows easy references to status badges and parity of the scripts it uses. The `on:` section obviates job creation and resource usage for irrelevant events. Notably, GitHub Actions includes the ability to skip documentation-only jobs. Here's a partial `test.yml` including only the aspects mentioned above. ```yaml on: # yamllint disable-line rule:truthy push: # non-tagged pushes to master branches: - master tags-ignore: - '*' paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json pull_request: # pull requests targeted at the master branch. branches: - master paths-ignore: - '**/*.md' - './build-bin/*lint' - ./build-bin/mlc_config.json jobs: test: steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Test run: | build-bin/configure_test build-bin/test ``` ## Deploy Deploy builds and pushes artifacts to a remote repository for master and release commits on it. CI providers deploy pushes to master on when the tag is blank, but not on documentation-only commits. Releases should deploy on version tags (ex `/^[0-9]+\.[0-9]+\.[0-9]+/`), without consideration of if the commit is documentation only or not. * [configure_deploy](configure_deploy) - Sets up environment and logs in. * [deploy](deploy) - deploys the project, with arg1 being "master" or a release commit like "1.2.3" ### Example GitHub Actions setup A simplest GitHub Actions `deploy.yml` deploys after logging in, but only on relevant event conditions. The name `deploy.yml` and job `deploy` allows easy references to status badges and parity of the scripts it uses. The `on:` section obviates job creation and resource usage for irrelevant events. GitHub Actions cannot implement "master, except documentation only-commits" in the same file. Hence, deployments of master will happen even on README change. Here's a partial `deploy.yml` including only the aspects mentioned above. Notice env variables are explicitly defined and `on.tags` is a [glob pattern](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet). ```yaml on: # yamllint disable-line rule:truthy push: branches: - master # Don't deploy tags because the same commit for MAJOR.MINOR.PATCH is also # on master: Redundant deployment of a release version will fail uploading. tags-ignore: - '*' jobs: deploy: runs-on: ubuntu-24.04 # newest available distribution, aka noble steps: - name: Checkout Repository uses: actions/checkout@v4 with: fetch-depth: 1 # only needed to get the sha label - name: Deploy env: GH_USER: ${{ secrets.GH_USER }} GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | # GITHUB_REF = refs/heads/master or refs/tags/MAJOR.MINOR.PATCH build-bin/configure_deploy && build-bin/deploy $(echo ${GITHUB_REF} | cut -d/ -f 3) ``` ================================================ FILE: build-bin/configure_deploy ================================================ #!/bin/sh -ue # This script sets up anything needed for `./deploy`. Do not assume `configure_test` was called. # # See [README.md] for an explanation of this and how CI should use it. build-bin/docker/configure_docker_push build-bin/gpg/configure_gpg # When arch isn't amd64 zipkin-lens needs offline install build-bin/maybe_install_npm build-bin/maven_go_offline # openzipkin/zipkin publishes Javadoc to https://zipkin.io/zipkin/ on release build-bin/git/login_git ================================================ FILE: build-bin/configure_lint ================================================ #!/bin/sh -ue # Attempt to install markdown-link-check if absent # Pinned until https://github.com/tcort/markdown-link-check/issues/369 markdown-link-check -V || npm install -g markdown-link-check@3.12.2 # Attempt to install yamllint if absent yamllint -v || pip install --user yamllint ================================================ FILE: build-bin/configure_test ================================================ #!/bin/sh -ue # This script sets up anything needed for `./test`. This should not login to anything, as that # should be done in `configure_deploy`. # # See [README.md] for an explanation of this and how CI should use it. build-bin/docker/configure_docker # When arch isn't amd64 zipkin-lens needs offline install build-bin/maybe_install_npm build-bin/maven_go_offline ================================================ FILE: build-bin/deploy ================================================ #!/bin/sh -ue # This script deploys a master or release version. # # See [README.md] for an explanation of this and how CI should use it. version=${1:-master} # Use implicit version when master, if we can.. if [ "${version}" = "master" ]; then version=$(sed -En 's/.*(.*)<\/version>.*/\1/p' pom.xml| head -1) fi build-bin/maven/maven_deploy export RELEASE_FROM_MAVEN_BUILD=true build-bin/docker_push ${version} # openzipkin/zipkin publishes Javadoc to gh-pages (https://zipkin.io/zipkin/) on release case ${version} in *-SNAPSHOT ) ;; * ) build-bin/javadoc_to_gh_pages ${version} ;; esac ================================================ FILE: build-bin/docker/configure_docker ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Defends against build outages caused by Docker Hub (docker.io) pull rate limits. # # It should not login to anything, as that should be done in `configure_docker_push` set -ue # The below sets up testcontainers configuration, which will be ignored if it isn't used. Even if # this is Docker related, it is coupled to integration tests configuration invoked with Maven. # * See https://www.testcontainers.org/supported_docker_environment/image_registry_rate_limiting/ # * checks.disable=true - saves time and a docker.io pull of alpine # * ryuk doesn't count against docker.io rate limits because Docker approved testcontainers as OSS echo checks.disable=true >> ~/.testcontainers.properties # We don't use any docker.io images, but add a Google's mirror in case something implicitly does # * See https://cloud.google.com/container-registry/docs/pulling-cached-images echo '{ "registry-mirrors": ["https://mirror.gcr.io"] }' | sudo tee /etc/docker/daemon.json sudo service docker restart ================================================ FILE: build-bin/docker/configure_docker_push ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Ensures Docker is logged in and it can build multi-architecture. # This should be used instead of `configure_docker` when a push will occur. # # This should only happen when we are publishing multi-arch builds, as otherwise the setup could use # Docker Hub pull quota and possibly cause a build outage. set -ue # Verify we are on an arch that can publish multi-arch images arch=$($(dirname "$0")/docker_arch) if [ "${arch}" != "amd64" ]; then >&2 echo "multiarch/qemu-user-static doesn't support arch ${arch}" exit 1 fi # Enable experimental features on the server (multi-arch) echo '{ "experimental":true, "registry-mirrors": ["https://mirror.gcr.io"] }' | sudo tee /etc/docker/daemon.json sudo service docker restart # Enable experimental client features (multi-arch) mkdir -p ${HOME}/.docker && echo '{"experimental":"enabled"}' > ${HOME}/.docker/config.json # Log in to GitHub Container Registry and Docker Hub for releasing images # This effects ${HOME}/.docker/config.json, which was created above # All images push to ghcr.io echo "${GH_TOKEN}" | docker login ghcr.io -u "${GH_USER}" --password-stdin # Some images push to docker.io: check first if credentials exist or not. if [ -n "${DOCKERHUB_USER:-}" ]; then echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USER}" --password-stdin fi # Enable execution of different multi-architecture containers by QEMU and binfmt_misc # See https://github.com/multiarch/qemu-user-static # # Mirrored image use to avoid docker.io pulls: # docker tag multiarch/qemu-user-static:7.2.0-1 ghcr.io/openzipkin/multiarch-qemu-user-static:latest # # Note: This image only works on x86_64/amd64 architecture. # See: https://github.com/multiarch/qemu-user-static#supported-host-architectures docker run --rm --privileged ghcr.io/openzipkin/multiarch-qemu-user-static --reset -p yes ================================================ FILE: build-bin/docker/docker-healthcheck ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # HEALTHCHECK for use in `docker ps`, `docker compose ps`, or a readiness probe in k8s. # # The following variables are read from ENV in the Dockerfile or env readable from pid 1. # * HEALTHCHECK_KIND - must be "http" or "tcp". Defaults to "http" # * When "http" the GET path to check is /health unless overridden by HEALTHCHECK_PATH # * HEALTHCHECK_IP - Defaults to $(hostname -i || 127.0.0.1) # * HEALTHCHECK_PORT - Not always the service port, but in Zipkin it typically is. No default # # Setup like this: # # We use a 30s start period to avoid marking the container unhealthy on slow or contended CI hosts # HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] # # Note: this is named docker-healthcheck, not docker_healthcheck like our other scripts. That's due # to conventions in https://github.com/docker-library/healthcheck/ # Fail on unset variables, but don't quit on rc!=0, so we can log what happened set -u +e # Export healthcheck variables we can read from pid 1. Some processes such as nginx wipe env. In # that case, this will still be able to see variables set as ENV instructions in the Dockerfile. export `cat /proc/1/environ| tr '\0' '\n'| \ egrep '^(HEALTHCHECK_KIND|HEALTHCHECK_IP|HEALTHCHECK_PATH|HEALTHCHECK_PORT)='` kind=${HEALTHCHECK_KIND:-http} ip=${HEALTHCHECK_IP:-$(hostname -i || echo '127.0.0.1')} port=${HEALTHCHECK_PORT?-is required} case ${kind} in http ) path=${HEALTHCHECK_PATH:-/health} endpoint="http://${ip}:${port}${path}" # Use b3:0 to ensure health checks aren't traced # Use timeout 1s (-T 1) as the health check interval is often 1s out=$(wget -T 1 -qO- "${endpoint}" --header=b3:0 2>&1) rc=$? ;; tcp ) # Use timeout 1s (-w 1) as the health check interval is often 1s out=$(nc -w 1 -z ${ip} ${port} 2>&1) rc=$? ;; * ) >&2 echo "Invalid HEALTHCHECK_KIND: ${kind}" exit 1 ;; esac if [ "$rc" = "0" ]; then exit 0; fi >&2 echo "Health check failed with code ${rc} response: ${out}" exit 1 ================================================ FILE: build-bin/docker/docker_arch ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script gets a normalized name for the architecture as used in Docker. This will be a subset # of ones supported by buildx: https://github.com/docker/buildx/releases. This is a subset because # for us to support a platform implies also supporting things like running NPM on it, so it should # be an explicit act to add a platform. set -ue # Normalize docker_arch to what's available # # Note: s390x and ppc64le were added for Knative docker_arch=${DOCKER_ARCH:-$(uname -m)} case ${docker_arch} in amd64* ) docker_arch=amd64 ;; x86_64* ) docker_arch=amd64 ;; arm64* ) docker_arch=arm64 ;; aarch64* ) docker_arch=arm64 ;; s390x* ) docker_arch=s390x ;; ppc64le* ) docker_arch=ppc64le ;; * ) >&2 echo "Unsupported DOCKER_ARCH: ${docker_arch}" exit 1; esac echo ${docker_arch} ================================================ FILE: build-bin/docker/docker_args ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This builds common docker arguments used by docker_build and docker_push. # This script checks each variable value, so it isn't important to fail on unbound (set -u) set -e docker_file=${DOCKER_FILE:-docker/Dockerfile} if ! test -f ${docker_file}; then >&2 echo "${docker_file} doesn't exist" exit 1 fi # Add default labels for the day and the SHA we're building from docker_args="${DOCKER_ARGS:-} -f ${docker_file} \ --label org.opencontainers.image.created=$(date +%Y-%m-%d) \ --label org.opencontainers.image.revision=$(git rev-parse --short HEAD)" version=${1:-} if [ -n "${version}" ]; then docker_args="${docker_args} --build-arg version=${version}" fi # When true, the artifact searched with `build-bin/maven/unjar` is in the context root # Ensure .dockerignore allows the artifacts intended. if [ "${RELEASE_FROM_MAVEN_BUILD}" = "true" ]; then docker_args="${docker_args} --build-arg release_from_maven_build=true" fi # When non-empty the target to build. if [ -n "${DOCKER_TARGET}" ]; then docker_args="${docker_args} --target ${DOCKER_TARGET}" fi # When non-empty, becomes the base layer including tag appropriate for the image being built. # e.g. ghcr.io/openzipkin/java:21.0.6_p7-jre # # This is not required to be a base (FROM scratch) image like ghcr.io/openzipkin/alpine:3.12.3 # See https://docs.docker.com/glossary/#parent-image if [ -n "${DOCKER_PARENT_IMAGE}" ]; then docker_args="${docker_args} --build-arg docker_parent_image=${DOCKER_PARENT_IMAGE}" fi # When non-empty, becomes the build-arg alpine_version. e.g. "3.12.3" # Used to align base layers from https://github.com/orgs/openzipkin/packages/container/package/alpine if [ -n "${ALPINE_VERSION}" ]; then docker_args="${docker_args} --build-arg alpine_version=${ALPINE_VERSION}" fi # When non-empty, becomes the build-arg java_version. e.g. "21.0.6_p7" # Used to align base layers from https://github.com/orgs/openzipkin/packages/container/package/java if [ -n "${JAVA_VERSION}" ]; then docker_args="${docker_args} --build-arg java_version=${JAVA_VERSION}" # Only set java_home build arg when we control or can verify it. java_major_version=$(echo ${JAVA_VERSION}| cut -f1 -d .) case ${java_major_version} in 8) java_home=/usr/lib/jvm/java-1.8-openjdk;; 1?|2?|3?) java_home=/usr/lib/jvm/java-${java_major_version}-openjdk;; esac if [ -n "${java_home}" ]; then docker_args="${docker_args} --build-arg java_home=${java_home}"; fi fi # When non-empty, becomes the build-arg maven_classifier. e.g. "module" or "exec" # Used as the classifier arg to ./build-bin/maven/maven_unjar. Allows building two images with the # same Dockerfile, varying on classifier, like openzipkin/zipkin vs openzipkin/zipkin-slim if [ -n "${MAVEN_CLASSIFIER}" ]; then docker_args="${docker_args} --build-arg maven_classifier=${MAVEN_CLASSIFIER}" fi echo ${docker_args} ================================================ FILE: build-bin/docker/docker_block_on_health ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Blocks until a named docker container with a valid HEALTHCHECK instruction is healthy or not: set -ue container_name=${1?container_name is required} container_id=$(docker ps -q -f name=${container_name}) while status="$(docker inspect --format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}" "${container_id}")"; do case $status in starting) sleep 1;; healthy) exit 0;; unhealthy) exit 1;; esac done exit 1 ================================================ FILE: build-bin/docker/docker_build ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -ue docker_tag=${1?full docker_tag is required. Ex openzipkin/zipkin:test} version=${2:-} docker_args=$($(dirname "$0")/docker_args ${version}) # We don't need build kit, but Docker 20.10 no longer accepts --platform # without it. It is simpler to always enable it vs require maintainers to use # alternate OCI tools. See https://github.com/moby/moby/issues/41552 export DOCKER_BUILDKIT=1 echo "Building image ${docker_tag}" docker build --network=host --pull ${docker_args} --tag ${docker_tag} . ================================================ FILE: build-bin/docker/docker_push ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script pushes images to GitHub Container Registry (ghcr.io). # # When a release, and DOCKER_RELEASE_REPOS is unset they also push to Docker Hub (docker.io). # # Note: In CI, `configure_docker_push` must be called before invoking this. # # Avoid buildx on push for reasons including: # * Caching is more complex as builder instances must be considered # * Platform builds run in parallel, leading to port conflict failures (ex in cassandra) # * 0.4.2 multi-platform builds have failed due to picking the wrong image for a FROM instruction set -ue docker_image=${1?docker_image is required, notably without a tag. Ex openzipkin/zipkin} version=${2:-master} # We don't need build kit, but Docker 20.10 no longer accepts --platform # without it. It is simpler to always enable it vs require maintainers to use # alternate OCI tools. See https://github.com/moby/moby/issues/41552 export DOCKER_BUILDKIT=1 case ${version} in master ) is_release=false ;; *-SNAPSHOT ) is_release=false ;; * ) is_release=true ;; esac if [ "${is_release}" = "true" ]; then docker_tags=${DOCKER_TAGS:-} if [ -z "${docker_tags}" ]; then major_tag=$(echo "${version}" | cut -f1 -d. -s) minor_tag=$(echo "${version}" | cut -f1-2 -d. -s) subminor_tag="${version}" docker_tags="$subminor_tag $minor_tag $major_tag latest" fi docker_repos=${DOCKER_RELEASE_REPOS:-ghcr.io docker.io} else docker_tags=master docker_repos=ghcr.io fi tags="" for repo in ${docker_repos}; do tags="${tags}\n" for tag in ${docker_tags}; do tags="${tags} ${repo}/${docker_image}:${tag}" done done docker_args=$($(dirname "$0")/docker_args ${version}) # Note: s390x and ppc64le were added for Knative docker_archs=${DOCKER_ARCHS:-amd64 arm64 s390x ppc64le} echo "Will build the following architectures: ${docker_archs}" docker_tag0="$(echo ${docker_tags} | awk '{print $1;}')" docker_arch0="$(echo ${docker_archs} | awk '{print $1;}')" arch_tags="" for docker_arch in ${docker_archs}; do arch_tag=${docker_image}:${docker_tag0}-${docker_arch} echo "Building tag ${arch_tag}..." docker build --pull ${docker_args} --platform linux/${docker_arch} --tag ${arch_tag} . arch_tags="${arch_tags} ${arch_tag}" done echo "Will push the following tags:\n${tags}" if [ "${docker_arch0}" = "${docker_archs}" ]; then # single architecture arch_tag=${docker_image}:${docker_tag0}-${docker_arch0} for tag in $(echo ${tags} | xargs); do docker tag ${arch_tag} ${tag} echo "Pushing tag ${tag}..." docker push ${tag} done else # multi-architecture: make a manifest for tag in $(echo ${tags} | xargs); do manifest_tags="" for arch_tag in ${arch_tags}; do docker_arch=$(echo ${arch_tag} | sed 's/.*-//g') manifest_tag=${tag}-${docker_arch} docker tag ${arch_tag} ${manifest_tag} echo "Pushing tag ${manifest_tag}..." docker push ${manifest_tag} manifest_tags="${manifest_tags} ${manifest_tag}" done docker manifest create ${tag} ${manifest_tags} for manifest_tag in ${manifest_tags}; do docker_arch=$(echo ${manifest_tag} | sed 's/.*-//g') docker manifest annotate ${tag} ${manifest_tag} --os linux --arch ${docker_arch} done echo "Pushing manifest ${manifest_tag}..." docker manifest push -p ${tag} done fi ================================================ FILE: build-bin/docker/docker_test_image ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Tests a an image by awaiting its HEALTHCHECK. # # This can be made more sophisticated via docker-compose. For example, you can set a utility # container that issues `wget` against your actual container in its HEALTHCHECK. set -ue # export this variable so that docker compose can use it export DOCKER_IMAGE=${1?full docker_tag is required. Ex openzipkin/zipkin:test} # The two options are to run a single container of the image, or docker compose which includes it. docker_compose_file=${2:-build-bin/docker-compose-$(echo ${DOCKER_IMAGE}| sed 's~.*/\(.*\):.*~\1~g').yml} docker_container=${3:-sut} # First try to run the intended containers. health_rc=0 if test -f "${docker_compose_file}"; then docker compose -f "${docker_compose_file}" up --remove-orphans -d --quiet-pull || health_rc=1 else docker run --name ${docker_container} -d ${DOCKER_IMAGE} || health_rc=1 fi # Next, inspect the health. This is a blocking command which waits until HEALTHCHECK passes or not. # This will fail if the container isn't healthy or doesn't exist (ex compose failed before creation) if [ "${health_rc}" = "1" ] || ! build-bin/docker/docker_block_on_health ${docker_container}; then >&2 echo "*** Failed waiting for health of ${docker_container}" # Sadly, we can't `docker compose inspect`. This means we may not see the inspect output of the # container that failed in docker compose until this is revised to work around compose/issues/4155 docker inspect --format='{{json .State.Health.Log}}' ${docker_container} || true # Log any containers output to console before we remove them. if test -f "${docker_compose_file}"; then docker compose -f "${docker_compose_file}" logs else docker logs ${docker_container} || true fi health_rc=1 fi # Clean up any containers, so that we can run this again without conflict. if test -f "${docker_compose_file}"; then docker compose -f "${docker_compose_file}" down else docker kill ${docker_container} && docker rm ${docker_container} fi exit ${health_rc} ================================================ FILE: build-bin/docker-compose-zipkin-eureka.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # uses 2.4 so we can use condition: service_healthy version: "2.4" # Test both authenticated and unauthenticated, as if there is a Spring problem, # the latter will crash. We only need to use HEALTHCHECK for this. services: eureka: image: openzipkin/zipkin-eureka:test container_name: eureka sut: image: openzipkin/zipkin-eureka:test container_name: sut environment: EUREKA_USERNAME: testuser EUREKA_PASSWORD: testpassword depends_on: eureka: condition: service_healthy ================================================ FILE: build-bin/docker-compose-zipkin-ui.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # uses 2.4 so we can use condition: service_healthy version: "2.4" services: zipkin: # Use last build of Zipkin instead of adding a matrix build dependency image: ghcr.io/openzipkin/zipkin-slim:master container_name: zipkin # Use fixed service and container name 'sut; so our test script can copy/pasta sut: # This is the image just built. It is not in a remote repository. image: openzipkin/zipkin-ui:test container_name: sut environment: # This is the default value, set explicitly here for visibility ZIPKIN_BASE_URL: http://zipkin:9411 depends_on: zipkin: condition: service_healthy ================================================ FILE: build-bin/docker-compose-zipkin-uiproxy.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # uses 2.4 so we can use condition: service_healthy version: "2.4" services: zipkin: # Use last build of Zipkin instead of adding a matrix build dependency image: ghcr.io/openzipkin/zipkin-slim:master container_name: zipkin environment: ZIPKIN_UI_BASEPATH: /admin/zipkin # Use fixed service and container name 'sut; so our test script can copy/pasta sut: # This is the image just built. It is not in a remote repository. image: openzipkin/zipkin-ui:test container_name: sut environment: ZIPKIN_UI_BASEPATH: /admin/zipkin depends_on: zipkin: condition: service_healthy ================================================ FILE: build-bin/docker-compose-zipkin.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # uses 2.4 so we can use condition: service_healthy version: "2.4" services: zipkin: # Use last build of Zipkin instead of adding a matrix build dependency image: openzipkin/zipkin:test container_name: zipkin # Use fixed service and container name 'sut; so our test script can copy/pasta sut: container_name: sut image: ghcr.io/openzipkin/alpine:3.21.2 entrypoint: /bin/sh # Keep the container running until HEALTHCHECK passes command: "-c \"sleep 5m\"" healthcheck: # Return 0 when we can load the UI resources test: wget -qO- --spider http://zipkin:9411/zipkin/ depends_on: zipkin: condition: service_healthy ================================================ FILE: build-bin/docker_push ================================================ #!/bin/sh -ue # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Pushes docker as part of `deploy` or from a trigger tag version=${1:-master} # handle trigger pattern like /^docker-[0-9]+\.[0-9]+\.[0-9]+$/ case ${version} in docker-* ) version=$(build-bin/git/version_from_trigger_tag docker- ${version}) ;; esac build-bin/docker/docker_push openzipkin/zipkin ${version} DOCKER_TARGET=zipkin-slim build-bin/docker/docker_push openzipkin/zipkin-slim ${version} # testing images only push to ghcro.io export DOCKER_RELEASE_REPOS=ghcr.io # Don't attempt unfamiliar archs on test images export DOCKER_ARCHS="amd64 arm64" for name in $(ls docker/test-images/*/Dockerfile|cut -f3 -d/); do DOCKER_FILE=docker/test-images/${name}/Dockerfile build-bin/docker/docker_push openzipkin/${name} ${version} done ================================================ FILE: build-bin/git/login_git ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -ue # Allocate commits to CI, not the owner of the deploy key git config user.name "zipkinci" git config user.email "zipkinci+zipkin-dev@googlegroups.com" # Setup https authentication credentials, used by ./mvnw release:prepare git config credential.helper "store --file=.git/credentials" echo "https://$GH_TOKEN:@github.com" > .git/credentials ================================================ FILE: build-bin/git/version_from_trigger_tag ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script echos a `MAJOR.MINOR.PATCH` version tag based on.. # * arg1: XXXXX- prefix # * arg2: XXXXX-MAJOR.MINOR.PATCH git trigger tag # # The script exits 1 if the prefix doesn't match. On success, the tag is deleted if it exists. # # Note: In CI, `build-bin/git/login_git` must be called before invoking this. set -ue trigger_tag_prefix=${1?required. Ex docker- to match docker-1.2.3} trigger_tag=${2?trigger_tag is required. Ex ${trigger_tag_prefix}1.2.3} # Checking sed output to determine success as exit code handling in sed or awk is awkward version=$(echo "${trigger_tag}" | sed -En "s/^${trigger_tag_prefix}([0-9]+\.[0-9]+\.[0-9]+)$/\1/p") if [ -z "$version" ]; then >&2 echo invalid trigger tag: ${trigger_tag} exit 1; fi # try to cleanup the trigger tag if it exists, but don't fail if it doesn't git tag -d ${trigger_tag} 2>&- >&- || true git push origin :${trigger_tag} 2>&- >&- || true echo $version ================================================ FILE: build-bin/gpg/configure_gpg ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script prepares GPG, needed to sign jars for Sonatype deployment during `maven_deploy` set -ue # ensure GPG commands work non-interactively export GPG_TTY=$(tty) # import signing key used for jar files echo ${GPG_SIGNING_KEY} | base64 --decode | gpg --batch --passphrase ${GPG_PASSPHRASE} --import ================================================ FILE: build-bin/javadoc_to_gh_pages ================================================ #!/bin/sh -ue # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script creates JavaDoc for a version and pushes an update to gh-pages. This fails if there # are no javadoc jars in the current directory. # # This leaves the git context on gh-pages when done. For this reason, it is better to run this at # the end of deployment. version=${1?version is required. Ex 2.22.2} rm -rf javadoc-builddir builddir="javadoc-builddir/${version}" javadoc_jars=$(find . -name "*${version}-javadoc.jar") if [ -z "${javadoc_jars}"]; then >&2 echo Incorrect state. javadoc jars should have been built before invoking this. exit 1 fi # Collect javadoc for all modules for jar in ${javadoc_jars}; do module="$(echo "$jar" | sed "s~.*/\(.*\)-${version}-javadoc.jar~\1~")" this_builddir="$builddir/$module" if [ -d "$this_builddir" ]; then # Skip modules we've already processed. # We may find multiple instances of the same javadoc jar because of, for instance, # integration tests copying jars around. continue fi mkdir -p "$this_builddir" unzip "$jar" -d "$this_builddir" # Build a simple module-level index echo "

  • ${module}
  • " >> "${builddir}/index.html" done # Update gh-pages git fetch origin gh-pages:gh-pages git checkout gh-pages rm -rf "${version}" mv "javadoc-builddir/${version}" ./ rm -rf "javadoc-builddir" # Update simple version-level index if ! grep "${version}" index.html 2>/dev/null; then echo "
  • ${version}
  • " >> index.html fi # Ensure links are ordered by versions, latest on top sort -rV index.html > index.html.sorted mv index.html.sorted index.html git add "${version}" git add index.html git commit -m "Automatically updated javadocs for ${version}" git push origin gh-pages ================================================ FILE: build-bin/lint ================================================ #!/bin/sh -ue yamllint --format github .github/workflows/*.yml find . -name \*.md |grep -v node|xargs markdown-link-check -c ./build-bin/mlc_config.json ================================================ FILE: build-bin/maven/maven_build ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -ue export MAVEN_OPTS="$($(dirname "$0")/maven_opts)" maven_goal=${MAVEN_GOAL:-package} if [ -x ./mvnw ]; then alias mvn=${PWD}/mvnw; fi ( if [ "${MAVEN_PROJECT_BASEDIR:-.}" != "." ]; then cd ${MAVEN_PROJECT_BASEDIR}; fi mvn -T1C -q --batch-mode -DskipTests "${maven_goal}" "$@" ) ================================================ FILE: build-bin/maven/maven_build_or_unjar ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -ue # Like `maven_unjar`, except when the version ends in SNAPSHOT and the artifact isn't in the $PWD, # it is built on-demand using `mvn package`. MAVEN_PROJECT_BASEDIR is used when the version arg or # artifact is missing. # # Note: Be careful building on-demand within Docker, as it can cause high bandwidth usage, pulling # dependencies for the project without the benefit of caching group_id=${1?group_id is required} artifact_id=${2?artifact_id is required} version=${3?version is required} classifier=${4:-} pom="${MAVEN_PROJECT_BASEDIR:-.}/pom.xml" # Use implicit version when master, if we can.. if [ "${version}" = "master" ] && [ -f "${pom}" ]; then version=$(sed -En 's/.*(.*)<\/version>.*/\1/p' ${pom}| head -1) fi # rebuild the args with the version set. args="${group_id} ${artifact_id} ${version} ${classifier}" # Fail on unset variables, but don't quit on rc!=0, so we can log what happened set -u +e unjar_out=$($(dirname "$0")/maven_unjar $args 2>&1) unjar_rc=$? if [ "${unjar_rc}" = "0" ] || [ -z "${pom:-}" ]; then exit 0; fi case ${version} in *-SNAPSHOT ) ;; * ) >&2 echo ${unjar_out} exit ${unjar_rc} ;; esac set -ue echo "*** Building snapshot from source..." $(dirname "$0")/maven_build -pl :${artifact_id} --am $(dirname "$0")/maven_unjar $args ================================================ FILE: build-bin/maven/maven_deploy ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -ue export MAVEN_OPTS="$($(dirname "$0")/maven_opts)" # This script deploys a SNAPSHOT or release version to Sonatype. # # Note: In CI, `configure_maven_deploy` must be called before invoking this. ./mvnw --batch-mode -s ./.settings.xml -Prelease -nsu -DskipTests clean deploy $@ ================================================ FILE: build-bin/maven/maven_go_offline ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This is a go-offline that properly works with multi-module builds set -ue export MAVEN_OPTS="$($(dirname "$0")/maven_opts)" if [ -x ./mvnw ]; then alias mvn=${PWD}/mvnw; fi ( if [ "${MAVEN_PROJECT_BASEDIR:-.}" != "." ]; then cd ${MAVEN_PROJECT_BASEDIR}; fi mvn -q --batch-mode -nsu -Prelease de.qaware.maven:go-offline-maven-plugin:resolve-dependencies "$@" ) ================================================ FILE: build-bin/maven/maven_opts ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script checks each variable value, so it isn't important to fail on unbound (set -u) set -e maven_project_basedir=${MAVEN_PROJECT_BASEDIR:-.} pom="${maven_project_basedir}/pom.xml" # fail if there's no pom test -f "${pom}" arch=$(uname -m) case ${arch} in arm64* ) arch=arm64 ;; aarch64* ) arch=arm64 ;; esac maven_opts="${MAVEN_OPTS:-}" if [ ${arch} = "arm64" ] && [ -f /etc/alpine-release ]; then # Defensively avoid arm64+alpine problems with posix_spawn maven_opts="${maven_opts} -Djdk.lang.Process.launchMechanism=vfork" fi echo ${maven_opts} ================================================ FILE: build-bin/maven/maven_release ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -ue # This script creates a git `MAJOR.MINOR.PATCH` version tag which later will have `deploy` run against it. # # In CI.. # * trigger pattern: tag =~ /^release-[0-9]+\.[0-9]+\.[0-9]+/ # * build-bin/git/login_git must be called before invoking this. export MAVEN_OPTS="$($(dirname "$0")/maven_opts)" trigger_tag=${1?trigger_tag is required. Ex release-1.2.3} release_version=$(build-bin/git/version_from_trigger_tag release- ${trigger_tag}) release_branch=${2:-master} # Checkout master, as we release from master, not a tag ref git fetch --no-tags --prune --depth=1 origin +refs/heads/${release_branch}:refs/remotes/origin/${release_branch} git checkout ${release_branch} # Ensure no one pushed commits since this release tag as it would fail later commands commit_local_release_branch=$(git show --pretty='format:%H' ${release_branch}) commit_remote_release_branch=$(git show --pretty='format:%H' origin/${release_branch}) if [ "$commit_local_release_branch" != "$commit_remote_release_branch" ]; then >&2 echo "${release_branch} on remote 'origin' has commits since the version to release, aborting" exit 1 fi # Prepare and push release commits and the version tag (N.N.N), which triggers deployment. ./mvnw --batch-mode -nsu -DreleaseVersion=${release_version} -Denforcer.fail=false -Darguments="-DskipTests -Denforcer.fail=false" release:prepare ================================================ FILE: build-bin/maven/maven_unjar ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script gets one jar from Maven, most typically an exec or module jar, extracting its contents # to a directory corresponding to the artifact_id parameter. # # Ex. ./maven_unjar io.zipkin zipkin-server 2.22.2 slim # # This searches ${MAVEN_PROJECT_BASEDIR} when set instead of resolving remotely. # # Note: If you are running this within Docker, ensure you have .dockerignore setup properly to pass # the artifact you are looking for. # # Ex. # !./module/target/zipkin-module-*-module.jar set -eu export MAVEN_OPTS="$($(dirname "$0")/maven_opts)" if [ -x ./mvnw ]; then alias mvn=${PWD}/mvnw; fi group_id=${1?group_id is required} artifact_id=${2?artifact_id is required} version=${3?version is required} classifier=${4:-} if [ -n "${classifier}" ]; then qualified_jar=${artifact_id}-${version}-${classifier}.jar else qualified_jar=${artifact_id}-${version}.jar fi case ${version} in *-SNAPSHOT ) is_release=false ;; * ) is_release=true ;; esac # Parse MAVEN_OPTS as it may have overridden .m2/repository local_repo=$(echo ${MAVEN_OPTS} | sed -n 's/.*maven.repo.local=\([^ ]*\).*/\1/p') if [ "${local_repo}" = "" ]; then local_repo=$HOME/.m2/repository; fi local_repo_path=${local_repo}/$(echo ${group_id} | tr '.' '/')/${artifact_id}/${version}/${qualified_jar} if test -f ${local_repo_path}; then echo "*** Reusing ${qualified_jar} from Maven local repository..." cp ${local_repo_path} ${artifact_id}.jar elif [ -n "${MAVEN_PROJECT_BASEDIR:-}" ]; then echo "*** Searching for ${qualified_jar} in ${MAVEN_PROJECT_BASEDIR}" find ${MAVEN_PROJECT_BASEDIR} -name ${qualified_jar} -exec cp {} ${artifact_id}.jar \; fi if ! test -f ${artifact_id}.jar && [ ${is_release} = "true" ]; then mvn_get="mvn -q --batch-mode -Denforcer.fail=false \ org.apache.maven.plugins:maven-dependency-plugin:3.8.1:get \ -Dtransitive=false -DgroupId=${group_id} -DartifactId=${artifact_id} -Dversion=${version}" if [ -n "${classifier}" ]; then mvn_get="${mvn_get} -Dclassifier=${classifier}" fi echo "*** Resolving ${qualified_jar} from Maven default repositories..." ${mvn_get} || true # Don't add load to Sonatype releases repository except when central sync is delayed if ! test -f ${local_repo_path}; then echo "*** Resolving ${qualified_jar} from Maven Sonatype releases repository..." ${mvn_get} -DremoteRepositories=https://oss.sonatype.org/content/repositories/releases fi cp ${local_repo_path} ${artifact_id}.jar fi if ! test -f ${artifact_id}.jar; then >&2 echo "*** Failed to build or get ${qualified_jar}" exit 1 fi (mkdir ${artifact_id} && cd ${artifact_id} && jar -xf ../${artifact_id}.jar) && rm ${artifact_id}.jar ================================================ FILE: build-bin/maven_go_offline ================================================ #!/bin/sh -ue # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # build-bin/maven/maven_go_offline export MAVEN_OPTS="$(build-bin/maven/maven_opts)" # Prefetch dependencies used by zipkin-ui (NPM and NodeJS binary and dependencies of our build) ./mvnw -q --batch-mode -nsu -pl zipkin-lens generate-resources ================================================ FILE: build-bin/maybe_install_npm ================================================ #!/bin/sh -ue # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This script hydrates the Maven and NPM cache to make later processes operate with less chance of # network problems. arch=$(uname -m) case ${arch} in arm64* ) arch=arm64 ;; aarch64* ) arch=arm64 ;; esac # ARM64 is not supported with musl, yet https://github.com/nodejs/node/blob/master/BUILDING.md # Workaround this by installing node and npm directly. See issue #3166 if [ ${arch} = "arm64" ] && [ -f /etc/alpine-release ]; then export MAVEN_OPTS="$($(dirname "$0")/maven/maven_opts)" if [ -x ./mvnw ]; then alias mvn=${PWD}/mvnw; fi if [ "${MAVEN_PROJECT_BASEDIR:-.}" != "." ]; then cd ${MAVEN_PROJECT_BASEDIR}; fi # Get the version of nodejs the build uses. Note: this takes time as it downloads Maven plugins. node_version=$(mvn help:evaluate -Dexpression=node.version -DskipTests -q -DforceStdout -pl zipkin-lens) set -x # Repos for https://pkgs.alpinelinux.org/packages?name=nodejs are already in the base image. apk add --update --no-cache nodejs=~${node_version} npm fi ================================================ FILE: build-bin/mlc_config.json ================================================ { "ignorePatterns": [ { "pattern": "https://chromewebstore.google.com/detail/*" }, { "pattern": "https://stackoverflow.com/questions/tagged/spring-boot" }, { "pattern": "https://oss.sonatype.org/content/repositories/snapshots" }, { "pattern": "http://localhost:9411/api/v[12]/spans" }, { "pattern": "http://localhost:9411/zipkin" }, { "pattern": "http://localhost/admin/zipkin/" }, { "pattern": "http://zipkin:9411" }, { "pattern": "http://localhost:9411/zipkin?serviceName=backend" }, { "pattern": "http://localhost:8081" }, { "pattern": "http://localhost:9000/api" }, { "pattern": "http://localhost:3000" } ] } ================================================ FILE: build-bin/test ================================================ #!/bin/sh -ue # This script runs the tests of the project. # # See [README.md] for an explanation of this and how CI should use it. # -DskipActuator ensures no tests rely on the actuator library ./mvnw -T1C verify -nsu -DskipActuator "$@" ================================================ FILE: docker/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime base layers of zipkin and zipkin-slim. # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/start-zipkin /docker-bin/ COPY . /code/ # This version is only used during the install process. Try to be consistent as it reduces layers, # which reduces downloads. FROM ghcr.io/openzipkin/java:${java_version} as install WORKDIR /code # Conditions aren't supported in Dockerfile instructions, so we copy source even if it isn't used. COPY --from=scratch /code/ . WORKDIR /install # When true, build-bin/maven/unjar searches /code for the artifact instead of resolving remotely. # /code contains what is allowed in .dockerignore. On problem, ensure .dockerignore is correct. ARG release_from_maven_build=false ENV RELEASE_FROM_MAVEN_BUILD=$release_from_maven_build # Version of the artifact to unjar. Ex. "2.4.5" or "2.4.5-SNAPSHOT" "master" to use the pom version. ARG version=master ENV VERSION=$version ENV MAVEN_PROJECT_BASEDIR=/code ARG TARGETARCH RUN test "${TARGETARCH}" != "" && \ if [ "${RELEASE_FROM_MAVEN_BUILD}" == "false" ]; then /code/build-bin/maybe_install_npm; fi; \ /code/build-bin/maven/maven_build_or_unjar io.zipkin zipkin-server ${VERSION} exec && \ mv zipkin-server zipkin && \ /code/build-bin/maven/maven_build_or_unjar io.zipkin zipkin-server ${VERSION} slim && \ mv zipkin-server zipkin-slim && \ # Copy tcnative deps to slim: 1.1MB more libs when pared to current platform. cp zipkin/BOOT-INF/lib/*tcnative*.jar zipkin-slim/BOOT-INF/lib/ && \ # Remove any unused platform-specific jars. This results in none for s390x or non-linux. rm */BOOT-INF/lib/*windows* */BOOT-INF/lib/*osx* && \ if [ "${TARGETARCH}" != "amd64" ]; then rm */BOOT-INF/lib/*x86*; fi && \ if [ "${TARGETARCH}" != "arm64" ]; then rm */BOOT-INF/lib/*a64* */BOOT-INF/lib/*aarch*; fi # Almost everything is common between the slim and normal build FROM ghcr.io/openzipkin/java:${java_version}-jre as base-server # All content including binaries and logs write under WORKDIR ARG USER=zipkin WORKDIR /${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts. # # If in production, you have a 30s startup, please report to https://gitter.im/openzipkin/zipkin # including the values of the /health and /info endpoints as this would be unexpected. HEALTHCHECK --interval=5s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-zipkin"] # Switch to the runtime user USER ${USER} FROM base-server as zipkin-slim LABEL org.opencontainers.image.description="Zipkin slim distribution on OpenJDK and Alpine Linux" COPY --from=install --chown=${USER} /install/zipkin-slim/ /zipkin/ EXPOSE 9411 FROM base-server as zipkin LABEL org.opencontainers.image.description="Zipkin full distribution on OpenJDK and Alpine Linux" # 3rd party modules like zipkin-aws will apply profile settings with this ENV MODULE_OPTS= COPY --from=install --chown=${USER} /install/zipkin/ /zipkin/ # Zipkin's full distribution includes Scribe support (albeit disabled) EXPOSE 9410 9411 ================================================ FILE: docker/RATIONALE.md ================================================ # zipkin-docker rationale ## Why do we add HEALTHCHECK? While most of our images are not production, we still add HEALTHCHECK for a better ad-hoc and automation experience. Many of our setups will not operate without a service dependency: having HEALTHCHECK present makes triage and scripting a bit easier. HEALTHCHECK on our test image serves primarily three purposes: * ad-hoc or scripted status of health using docker ps instead of knowing Kafka commands * to allow manual usage of the docker compose v2 service_healthy condition * support Docker Hub automated test service Ex. The following command can be used ad-hoc or in scripts without the user knowing Kafka: ```bash $ docker inspect --format='{{json .State.Health.Status}}' kafka-zookeeper "unhealthy" ``` ### Why do we change default timeouts? We changed timeouts in order to mark success faster. By default, the Docker health check runs after 30s, and if a failure occurs, it waits 30s to try again. This implies a minimum of 30s before the server is marked healthy. https://docs.docker.com/engine/reference/builder/#healthcheck We expect the server startup to take less than 10 seconds, even in a fresh start. Some health checks will trigger a slow "first request" due to schema setup (ex this is the case in Elasticsearch and Cassandra). However, we don't want to force an initial delay of 30s as defaults would. Instead, we lower the interval and timeout from 30s to 5s. If a server starts in 7s and takes another 2s to install schema, it can still pass in 10s vs 30s. We retain the 30s even if it would be an excessively long startup. This is to accommodate test containers, which can boot slower than production sites, and any knock-on effects of that, like slow dependent storage containers which are simultaneously bootstrapping. ### Why default timeout to 5s for Kafka HEALTHCHECK? Docker `HEALTHCHECK` marks a container unhealthy on the first failure that occurs past its start period. It is important to avoid false negatives that are host contention in nature, as they can break orchestration such as docker-compose. Commands like `nc` will almost never timeout launching due to contention, even if they might timeout on network connections themselves for the same reason. `kafka-topics.sh` uses Java and a relatively large classpath. It can be slow when many other processes are starting at the same time. For example, when launching many containers in Docker Compose, `kafka-topics.sh` timed out after 2s even though the prior run succeeded. This broke a condition which broke the rest of the automation. A 5s timeout is excessive usually, but avoided this problem. ================================================ FILE: docker/README.md ================================================ ## zipkin Docker images This directory contains assets used to build and release Zipkin's Docker images. ## Production images The only Zipkin production images built here: * openzipkin/zipkin: The core server image that hosts the Zipkin UI, Api and Collector features. * Mirrored as ghcr.io/openzipkin/zipkin * openzipkin/zipkin-slim: The stripped server image that hosts the Zipkin UI and Api features, but only supports in-memory or Elasticsearch storage with HTTP or gRPC span collectors. * Mirrored as ghcr.io/openzipkin/zipkin-slim ## Testing images We also provide a number images that are not for production, rather to simplify demos and integration tests. We designed these to be small and start easily. We did this by re-using the same base layer `openzipkin/zipkin`, and setting up schema where relevant. * [ghcr.io/openzipkin/zipkin-activemq](test-images/zipkin-activemq/README.md) - runs ActiveMQ Classic * [ghcr.io/openzipkin/zipkin-cassandra](test-images/zipkin-cassandra/README.md) - runs Cassandra initialized with Zipkin's schema * [ghcr.io/openzipkin/zipkin-elasticsearch7](test-images/zipkin-elasticsearch7/README.md) - runs Elasticsearch 7.x * [ghcr.io/openzipkin/zipkin-elasticsearch8](test-images/zipkin-elasticsearch8/README.md) - runs Elasticsearch 8.x * [ghcr.io/openzipkin/zipkin-opensearch2](test-images/zipkin-opensearch2/README.md) - runs OpenSearch 2.x * [ghcr.io/openzipkin/zipkin-eureka](test-images/zipkin-eureka/README.md) - runs Eureka * [ghcr.io/openzipkin/zipkin-kafka](test-images/zipkin-kafka/README.md) - runs both Kafka+ZooKeeper * [ghcr.io/openzipkin/zipkin-mysql](test-images/zipkin-mysql/README.md) - runs MySQL initialized with Zipkin's schema * [ghcr.io/openzipkin/zipkin-pulsar](test-images/zipkin-pulsar/README.md) - runs Pulsar * [ghcr.io/openzipkin/zipkin-rabbitmq](test-images/zipkin-rabbitmq/README.md) - runs RabbitMQ * [ghcr.io/openzipkin/zipkin-ui](test-images/zipkin-ui/README.md) - serves the (Lens) UI directly with NGINX ## Getting started Zipkin has no dependencies, for example you can run an in-memory zipkin server like so: ```bash # Note: this is mirrored as ghcr.io/openzipkin/zipkin-slim $ docker run -d -p 9411:9411 openzipkin/zipkin-slim ``` See the ui at (docker ip):9411 In the UI - click zipkin-server, then click "Find Traces". We also provide [example compose files](examples/README.md) that integrate collectors and storage, such as Kafka or Elasticsearch. ## Configuration Configuration is via environment variables, defined by [zipkin-server](../zipkin-server/README.md). Notably, you'll want to look at the `STORAGE_TYPE` environment variables, which include "cassandra", "mysql" and "elasticsearch". Note: the `openzipkin/zipkin-slim` image only supports "elasticsearch" storage. To use other storage types, you must use the main image `openzipkin/zipkin`. When in Docker, the following environment variables also apply * `JAVA_OPTS`: Use to set java arguments, such as heap size or trust store location. * By default, `openzipkin/zipkin` sets max heap to 64m while `openzipkin/zipkin-slim` 32m * `STORAGE_PORT_9042_TCP_ADDR` -- A Cassandra node listening on port 9042. This environment variable is typically set by linking a container running `zipkin-cassandra` as "storage" when you start the container. * `STORAGE_PORT_3306_TCP_ADDR` -- A MySQL node listening on port 3306. This environment variable is typically set by linking a container running `zipkin-mysql` as "storage" when you start the container. * `STORAGE_PORT_9200_TCP_ADDR` -- An Elasticsearch node listening on port 9200. This environment variable is typically set by linking a container running `zipkin-elasticsearch` as "storage" when you start the container. This is ignored when `ES_HOSTS` or `ES_AWS_DOMAIN` are set. * `KAFKA_PORT_2181_TCP_ADDR` -- A zookeeper node listening on port 2181. This environment variable is typically set by linking a container running `zipkin-kafka` as "kafka" when you start the container. For example, to increase heap size, set `JAVA_OPTS` as shown in our [docker-compose](examples/docker-compose.yml) file: ```yaml environment: - JAVA_OPTS=-Xms128m -Xmx128m -XX:+ExitOnOutOfMemoryError ``` For example, to add debug logging, set `command` as shown in our [docker-compose](examples/docker-compose.yml) file: ```yaml command: --logging.level.zipkin2=DEBUG ``` ## Runtime user The `openzipkin/zipkin` and `openzipkin/zipkin-slim` images run under a nologin user named 'zipkin' with a home directory of '/zipkin'. As this is Alpine Linux image, you won't find many utilities installed, but you can browse contents with a shell like below: ```bash $ docker run -it --rm --entrypoint /bin/sh openzipkin/zipkin /zipkin $ ls BOOT-INF META-INF org run.sh ``` ## Notes ### Container links If using Docker's deprecated container links, you need to set env variables accordingly. Ex. If your link name is "storage" for an Elasticsearch container: ``` ES_HOSTS=http://$STORAGE_PORT_9200_TCP_ADDR:9200 ``` The above is mentioned only for historical reasons. The OpenZipkin community do not support Docker's deprecated container links. ### MySQL If using an external MySQL server or image, ensure schema and other parameters match the [docs](../zipkin-storage/mysql-v1/README.md#applying-the-schema). ## Building images To build `openzipkin/zipkin:test`, from the top-level of the repository, run: ```bash $ build-bin/docker/docker_build openzipkin/zipkin:test ``` If you want the slim distribution (openzipkin/zipkin-slim:test), run: ```bash $ DOCKER_TARGET=zipkin-slim build-bin/docker/docker_build openzipkin/zipkin-slim:test ``` ================================================ FILE: docker/examples/.dockerignore ================================================ # https://docs.docker.com/engine/reference/builder/#dockerignore-file **/.* !./prometheus/create-datasource-and-dashboard.sh !./prometheus/prometheus.yml ================================================ FILE: docker/examples/README.md ================================================ # Zipkin Docker Examples This project is configured to run docker containers using [docker-compose](https://docs.docker.com/compose/). Note that the default configuration requires docker compose 1.6.0+ and docker-engine 1.10.0+. To start the default docker compose configuration, run: ```bash # To use the last released version of zipkin $ docker compose up # To use the last built version of zipkin $ TAG=master docker compose up ``` View the web UI at $(docker ip):9411. Traces are stored in memory. To see specific traces in the UI, select "zipkin-server" in the dropdown and then click the "Find Traces" button. ## ActiveMQ You can collect traces from [ActiveMQ](../test-images/zipkin-activemq/README.md) in addition to HTTP, using the `docker-compose-activemq.yml` file. This configuration starts `zipkin` and `zipkin-activemq` in their own containers. To add ActiveMQ configuration, run: ```bash $ docker compose -f docker-compose-activemq.yml up ``` Then configure the [ActiveMQ sender](https://github.com/openzipkin/zipkin-reporter-java/blob/master/activemq-client/src/main/java/zipkin2/reporter/activemq/ActiveMQSender.java) using a `brokerUrl` value of `failover:tcp://localhost:61616` or a non-local hostname if in docker. ## Cassandra You can store traces in [Cassandra](../test-images/zipkin-cassandra/README.md) instead of memory, using the `docker-compose-cassandra.yml` file. This configuration starts `zipkin`, `zipkin-cassandra` and `zipkin-dependencies` (cron job) in their own containers. To start the Cassandra-backed configuration, run: ```bash $ docker compose -f docker-compose-cassandra.yml up ``` The `zipkin-dependencies` container is a scheduled task that runs every hour. If you want to see the dependency graph before then, you can run it manually in another terminal like so: ```bash $ docker compose -f docker-compose-cassandra.yml run --rm --no-deps --entrypoint start-zipkin-dependencies dependencies ``` ## Elasticsearch You can store traces in [Elasticsearch](../test-images/zipkin-elasticsearch8/README.md) instead of memory, using the `docker-compose-elasticsearch.yml` file. This configuration starts `zipkin`, `zipkin-elasticsearch` and `zipkin-dependencies` (cron job) in their own containers. To start the Elasticsearch-backed configuration, run: ```bash $ docker compose -f docker-compose-elasticsearch.yml up ``` The `zipkin-dependencies` container is a scheduled task that runs every hour. If you want to see the dependency graph before then, you can run it manually in another terminal like so: ```bash $ docker compose -f docker-compose-elasticsearch.yml run --rm --no-deps --entrypoint start-zipkin-dependencies dependencies ``` ## Kafka You can collect traces from [Kafka](../test-images/zipkin-kafka/README.md) in addition to HTTP, using the `docker-compose-kafka.yml` file. This configuration starts `zipkin` and `zipkin-kafka` in their own containers. To add Kafka configuration, run: ```bash $ docker compose -f docker-compose-kafka.yml up ``` Then configure the [Kafka sender](https://github.com/openzipkin/zipkin-reporter-java/blob/master/kafka/src/main/java/zipkin2/reporter/kafka/KafkaSender.java) using a `bootstrapServers` value of `host.docker.internal:9092` if your application is inside the same docker network or `localhost:19092` if not, but running on the same host. In other words, if you are running a sample application on your laptop, you would use `localhost:19092` bootstrap server to send spans to the Kafka broker running in Docker. ### Docker machine and Kafka If you are using Docker machine, adjust `KAFKA_ADVERTISED_HOST_NAME` in `docker-compose-kafka.yml` and the `bootstrapServers` configuration of the kafka sender to match your Docker host IP (ex. 192.168.99.100:19092). ## MySQL You can store traces in [MySQL](../test-images/zipkin-mysql/README.md) instead of memory, using the `docker-compose-mysql.yml` file. This configuration starts `zipkin`, `zipkin-mysql` and `zipkin-dependencies` (cron job) in their own containers. To start the MySQL-backed configuration, run: ```bash $ docker compose -f docker-compose-mysql.yml up ``` ## RabbitMQ You can collect traces from [RabbitMQ](../test-images/zipkin-rabbitmq/README.md) in addition to HTTP, using the `docker-compose-rabbitmq.yml` file. This configuration starts `zipkin` and `zipkin-rabbitmq` in their own containers. To add RabbitMQ configuration, run: ```bash $ docker compose -f docker-compose-rabbitmq.yml up ``` Then configure the [RabbitMQ sender](https://github.com/openzipkin/zipkin-reporter-java/blob/master/amqp-client/src/main/java/zipkin2/reporter/amqp/RabbitMQSender.java) using a `host` value of `localhost` or a non-local hostname if in docker. ## Pulsar You can collect traces from [Pulsar](../test-images/zipkin-pulsar/README.md) in addition to HTTP, using the `docker-compose-pulsar.yml` file. This configuration starts `zipkin` and `zipkin-pulsar` in their own containers. To add Pulsar configuration, run: ```bash $ docker compose -f docker-compose-pulsar.yml up ``` ## Eureka You can register Zipkin for service discovery in [Eureka](../test-images/zipkin-eureka/README.md) using the `docker-compose-eureka.yml` file. This configuration starts `zipkin` and `zipkin-eureka` in their own containers. When `zipkin` starts, it registers its endpoint into `eureka`. Then, the two [example services](#example) discover zipkin's endpoint from `eureka` and use it to send spans. To try this out, run: ```bash $ docker compose -f docker-compose.yml -f docker-compose-eureka.yml up ``` ## Example The docker compose configuration can be extended to host an [example application](https://github.com/openzipkin/brave-example) using the `docker-compose-example.yml` file. That file employs [docker compose overrides](https://docs.docker.com/compose/extends/#multiple-compose-files) to add a "frontend" and "backend" service. To add the example configuration, run: ```bash $ docker compose -f docker-compose.yml -f docker-compose-example.yml up ``` Once the services start, open http://localhost:8081/ * This calls the backend (http://localhost:9000/api) and shows its result: a formatted date. Afterward, you can view traces that went through the backend via http://localhost:9411/zipkin?serviceName=backend ## UI The docker compose configuration can be extended to [host the UI](../test-images/zipkin-ui/README.md) on port 80 using the `docker-compose-ui.yml` file. That file employs [docker compose overrides](https://docs.docker.com/compose/extends/#multiple-compose-files) to add an NGINX container and relevant settings. To start the NGINX configuration, run: ```bash $ docker compose -f docker-compose.yml -f docker-compose-ui.yml up ``` This container doubles as a skeleton for creating proxy configuration around Zipkin like authentication, dealing with CORS with zipkin-js apps, or terminating SSL. If you want to run the zipkin-ui standalone against a remote zipkin server, you need to set `ZIPKIN_BASE_URL` accordingly: ```bash $ docker run -d -p 80:80 \ -e ZIPKIN_BASE_URL=http://myfavoritezipkin:9411 \ openzipkin/zipkin-ui ``` ## UI Proxy The docker compose configuration can be extended to [proxy the UI](../test-images/zipkin-uiproxy/README.md) on port 80 using the `docker-compose-uiproxy.yml` file. That file employs [docker compose overrides](https://docs.docker.com/compose/extends/#multiple-compose-files) to add an NGINX container and relevant settings. To start the NGINX configuration, run: ```bash $ docker compose -f docker-compose.yml -f docker-compose-uiproxy.yml up ``` This container helps verify the `ZIPKIN_UI_BASEPATH` variable by setting it to "/admin/zipkin". This means when the compose configuration is up, you can access Zipkin UI at http://localhost/admin/zipkin/ ## Prometheus Zipkin comes with a built-in Prometheus metric exporter. The `docker-compose-prometheus.yml` file starts Prometheus configured to scrape Zipkin, exposes it on port `9090`. You can open `$DOCKER_HOST_IP:9090` and start exploring metrics (available on the `/prometheus` endpoint of Zipkin). `docker-compose-prometheus.yml` also starts a Grafana with authentication disabled, exposing it on port 3000. On startup it's configured with the Prometheus instance started by `docker-compose` as a data source, and imports the dashboard published at https://grafana.com/dashboards/1598. This means that, after running `docker-compose ... -f docker-compose-prometheus.yml up`, you can open `$DOCKER_IP:3000/dashboard/db/zipkin-prometheus` and play around with the dashboard. ================================================ FILE: docker/examples/docker-compose-activemq.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml to add a test # activemq server, which is used as a span transport. version: '2.4' services: activemq: image: ghcr.io/openzipkin/zipkin-activemq:${TAG:-latest} container_name: activemq ports: # expose the ActiveMQ port so apps can publish spans. - "61616:61616" zipkin: extends: file: docker-compose.yml service: zipkin # slim doesn't include Activemq support, so switch to the larger image image: ghcr.io/openzipkin/zipkin:${TAG:-latest} environment: - ACTIVEMQ_URL=failover:tcp://activemq:61616 depends_on: activemq: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-cassandra.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml to run the # zipkin-cassandra container instead of the zipkin-mysql container. version: '2.4' services: storage: image: ghcr.io/openzipkin/zipkin-cassandra:${TAG:-latest} # Uncomment to use DSE instead (minimum version 5.1) # image: datastax/dse-server:5.1.20 # environment: # - DS_LICENSE=accept container_name: cassandra # Uncomment to expose the storage port for testing # ports: # - 9042:9042 # Use Cassandra instead of in-memory storage zipkin: extends: file: docker-compose.yml service: zipkin # slim doesn't include Cassandra support, so switch to the larger image image: ghcr.io/openzipkin/zipkin:${TAG:-latest} environment: - STORAGE_TYPE=cassandra3 # When using the test docker image, or have schema pre-installed, you don't need to ensure it - CASSANDRA_ENSURE_SCHEMA=false # When overriding this value, note the minimum supported version is 3.11.3 - CASSANDRA_CONTACT_POINTS=cassandra # Uncomment to configure authentication # - CASSANDRA_USERNAME=cassandra # - CASSANDRA_PASSWORD=cassandra # Uncomment to enable request logging (TRACE shows query values) # command: --logging.level.com.datastax.oss.driver.internal.core.tracker.RequestLogger=TRACE depends_on: storage: condition: service_healthy dependencies: extends: file: docker-compose-dependencies.yml service: dependencies environment: - STORAGE_TYPE=${STORAGE_TYPE:-cassandra3} - CASSANDRA_CONTACT_POINTS=cassandra depends_on: storage: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-dependencies.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # version: '2.4' services: # Adds a cron to process spans since midnight every hour, and all spans each day # This data is served by http://192.168.99.100:8080/dependency # # For more details, see https://github.com/openzipkin/docker-zipkin-dependencies dependencies: image: ghcr.io/openzipkin/zipkin-dependencies container_name: dependencies user: root entrypoint: /usr/sbin/crond -f # environment: # Uncomment to see dependency processing logs # - ZIPKIN_LOG_LEVEL=DEBUG # Uncomment to adjust memory used by the dependencies job # - JAVA_OPTS=-verbose:gc -Xms1G -Xmx1G ================================================ FILE: docker/examples/docker-compose-elasticsearch.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml to run the # zipkin-elasticsearch container instead of the zipkin-mysql container. version: '2.4' services: storage: image: ghcr.io/openzipkin/zipkin-elasticsearch8:${TAG:-latest} container_name: elasticsearch # Uncomment to expose the storage port for testing # ports: # - 9200:9200 # Use Elasticsearch instead of in-memory storage zipkin: extends: file: docker-compose.yml service: zipkin environment: - STORAGE_TYPE=elasticsearch # Point the zipkin at the storage backend - ES_HOSTS=elasticsearch:9200 # Uncomment to see requests to and from elasticsearch # - ES_HTTP_LOGGING=BODY depends_on: storage: condition: service_healthy dependencies: extends: file: docker-compose-dependencies.yml service: dependencies environment: - STORAGE_TYPE=elasticsearch - ES_HOSTS=elasticsearch depends_on: storage: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-eureka.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml to register # zipkin into a test eureka server. version: '2.4' services: eureka: image: ghcr.io/openzipkin/zipkin-eureka:${TAG:-latest} container_name: eureka # Uncomment to require authentication # environment: # - EUREKA_USERNAME=username # - EUREKA_PASSWORD=password # Uncomment to expose the eureka port for testing # ports: # - 8761:8761 zipkin: extends: file: docker-compose.yml service: zipkin environment: - EUREKA_SERVICE_URL=http://eureka:8761/eureka/v2 # Uncomment to authenticate eureka #- EUREKA_SERVICE_URL=http://username:password@eureka:8761/eureka/v2 - EUREKA_HOSTNAME=zipkin depends_on: eureka: condition: service_healthy # Generate traffic by hitting http://localhost:8081 frontend: image: ghcr.io/openzipkin/brave-example:armeria container_name: frontend entrypoint: start-frontend environment: - EUREKA_SERVICE_URL=http://eureka:8761/eureka/v2 # Uncomment to authenticate eureka #- EUREKA_SERVICE_URL=http://username:password@eureka:8761/eureka/v2 ports: - 8081:8081 depends_on: backend: condition: service_healthy zipkin: condition: service_healthy # Serves the /api endpoint the frontend uses backend: image: ghcr.io/openzipkin/brave-example:armeria container_name: backend entrypoint: start-backend environment: - EUREKA_SERVICE_URL=http://eureka:8761/eureka/v2 # Uncomment to authenticate eureka #- EUREKA_SERVICE_URL=http://username:password@eureka:8761/eureka/v2 depends_on: zipkin: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-example.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Format version 2.1 was introduced with Docker Compose v1.9 # We need Docker Compose v1.9+ for unset variable interpolation version: "2.1" services: # Generate traffic by hitting http://localhost:8081 frontend: container_name: frontend image: ghcr.io/openzipkin/brave-example:${PROJECT:-armeria} entrypoint: start-frontend ports: - 8081:8081 depends_on: backend: condition: service_healthy zipkin: condition: service_started # Serves the /api endpoint the frontend uses backend: container_name: backend image: ghcr.io/openzipkin/brave-example:${PROJECT:-armeria} entrypoint: start-backend depends_on: zipkin: condition: service_started ================================================ FILE: docker/examples/docker-compose-kafka.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml to add a test # kafka server, which is used as a span transport. version: '2.4' services: kafka: image: ghcr.io/openzipkin/zipkin-kafka:${TAG:-latest} container_name: kafka # If using docker machine, uncomment the below and set your bootstrap # server list to 192.168.99.100:19092 # environment: # - KAFKA_ADVERTISED_HOST_NAME=192.168.99.100 ports: # Processes on the Docker host can set bootstrap server list to localhost:19092 - 19092:19092 zipkin: extends: file: docker-compose.yml service: zipkin # slim doesn't include Kafka support, so switch to the larger image image: ghcr.io/openzipkin/zipkin:${TAG:-latest} environment: - KAFKA_BOOTSTRAP_SERVERS=kafka:9092 depends_on: kafka: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-mysql.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # This runs the zipkin and zipkin-mysql containers, using docker-compose's # default networking to wire the containers together. # # Note that this file is meant for learning Zipkin, not production deployments. version: '2.4' services: storage: image: ghcr.io/openzipkin/zipkin-mysql:${TAG:-latest} container_name: mysql # Uncomment to expose the storage port for testing # ports: # - 3306:3306 # Use MySQL instead of in-memory storage zipkin: extends: file: docker-compose.yml service: zipkin # slim doesn't include MySQL support, so switch to the larger image image: ghcr.io/openzipkin/zipkin:${TAG:-latest} environment: - STORAGE_TYPE=mysql - MYSQL_HOST=storage # Add the baked-in username and password for the zipkin-mysql image - MYSQL_USER=zipkin - MYSQL_PASS=zipkin depends_on: storage: condition: service_healthy dependencies: extends: file: docker-compose-dependencies.yml service: dependencies environment: - STORAGE_TYPE=mysql - MYSQL_HOST=storage # Add the baked-in username and password for the zipkin-mysql image - MYSQL_USER=zipkin - MYSQL_PASS=zipkin depends_on: storage: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-prometheus.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # This runs containers that collect data for our Grafana dashboard # # Note that this file is meant for learning Zipkin, not production deployments. version: '2.4' services: prometheus: # Use a quay.io mirror to prevent build outages due to Docker Hub pull quotas # Use latest from https://quay.io/repository/prometheus/prometheus?tab=tags image: quay.io/prometheus/prometheus:v2.55.1 container_name: prometheus ports: - 9090:9090 depends_on: zipkin: condition: service_healthy volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml grafana: # Use a quay.io mirror to prevent build outages due to Docker Hub pull quotas # Use latest from https://quay.io/repository/giantswarm/grafana?tab=tags image: quay.io/giantswarm/grafana:7.5.12 container_name: grafana ports: - 3000:3000 depends_on: - prometheus environment: - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin setup_grafana_datasource: # This is an arbitrary small image that has curl installed # Use a quay.io mirror to prevent build outages due to Docker Hub pull quotas # Use latest from https://quay.io/repository/cilium/alpine-curl?tab=tags image: quay.io/cilium/alpine-curl:v1.10.0 container_name: setup_grafana_datasource depends_on: - grafana volumes: - ./prometheus/create-datasource-and-dashboard.sh:/tmp/create.sh:ro working_dir: /tmp entrypoint: /tmp/create.sh ================================================ FILE: docker/examples/docker-compose-pulsar.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml to add a test # pulsar server, which is used as a span transport. version: '2.4' services: pulsar: image: ghcr.io/openzipkin/zipkin-pulsar:${TAG:-latest} container_name: pulsar ports: # expose the pulsar port so apps can publish spans. - "6650:6650" # - "8080:8080" # uncomment to expose the pulsar http port. zipkin: extends: file: docker-compose.yml service: zipkin # slim doesn't include Pulsar support, so switch to the larger image image: ghcr.io/openzipkin/zipkin:${TAG:-latest} environment: - PULSAR_SERVICE_URL=pulsar://pulsar:6650 depends_on: pulsar: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-rabbitmq.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml to add a test # rabbitmq server, which is used as a span transport. version: '2.4' services: rabbitmq: image: ghcr.io/openzipkin/zipkin-rabbitmq:${TAG:-latest} container_name: rabbitmq ports: # expose the rabbitmq port so apps can publish spans. - "5672:5672" zipkin: extends: file: docker-compose.yml service: zipkin # slim doesn't include RabbitMQ support, so switch to the larger image image: ghcr.io/openzipkin/zipkin:${TAG:-latest} environment: - RABBIT_ADDRESSES=rabbitmq:5672 depends_on: rabbitmq: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-ui.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml, hosting the # ui on port 80 using NGINX version: '2.4' services: zipkin-ui: image: ghcr.io/openzipkin/zipkin-ui:${TAG:-latest} container_name: zipkin-ui environment: # Change this if connecting to a different zipkin server - ZIPKIN_BASE_URL=http://zipkin:9411 ports: - 80:80 depends_on: zipkin: condition: service_healthy ================================================ FILE: docker/examples/docker-compose-uiproxy.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # It extends the default configuration from docker-compose.yml, hosting the # ui on port 80 using NGINX version: '2.4' services: zipkin-uiproxy: image: ghcr.io/openzipkin/zipkin-uiproxy:${TAG:-latest} container_name: zipkin-uiproxy environment: # This allows hitting the UI on the host by http://localhost/admin/zipkin - ZIPKIN_UI_BASEPATH=/admin/zipkin ports: - 80:80 depends_on: zipkin: condition: service_healthy zipkin: extends: file: docker-compose.yml service: zipkin environment: # This must match what's set in zipkin-uiproxy - ZIPKIN_UI_BASEPATH=/admin/zipkin ================================================ FILE: docker/examples/docker-compose.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # This file uses the version 2 docker compose file format, described here: # https://docs.docker.com/compose/compose-file/#version-2 # # This runs the zipkin slim container, using docker-compose's default networking # to wire other containers together. # # Note that this file is meant for learning Zipkin, not production deployments. version: '2.4' services: # The zipkin process services the UI, and also exposes a POST endpoint that # instrumentation can send trace data to. zipkin: image: ghcr.io/openzipkin/zipkin-slim:${TAG:-latest} container_name: zipkin # Environment settings are defined here https://github.com/openzipkin/zipkin/blob/master/zipkin-server/README.md#environment-variables environment: - STORAGE_TYPE=mem # Uncomment to enable self-tracing # - SELF_TRACING_ENABLED=true # Uncomment to increase heap size # - JAVA_OPTS=-Xms128m -Xmx128m -XX:+ExitOnOutOfMemoryError ports: # Port used for the Zipkin UI and HTTP Api - 9411:9411 # Uncomment to enable debug logging # command: --logging.level.zipkin2=DEBUG ================================================ FILE: docker/examples/prometheus/create-datasource-and-dashboard.sh ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -xeuo pipefail if ! curl --retry 5 --retry-connrefused --retry-delay 0 -sf http://grafana:3000/api/datasources/name/prom; then curl -sf -X POST -H "Content-Type: application/json" \ --data-binary '{"name":"prom","type":"prometheus","url":"http://prometheus:9090","access":"proxy","isDefault":true}' \ http://grafana:3000/api/datasources fi dashboard_id=1598 last_revision=$(curl -sf https://grafana.com/api/dashboards/${dashboard_id}/revisions | grep '"revision":' | sed 's/ *"revision": \([0-9]*\),/\1/' | sort -n | tail -1) echo '{"dashboard": ' > data.json curl -s https://grafana.com/api/dashboards/${dashboard_id}/revisions/${last_revision}/download >> data.json echo ', "inputs": [{"name": "DS_PROMETHEUS", "pluginId": "prometheus", "type": "datasource", "value": "prom"}], "overwrite": false}' >> data.json curl --retry-connrefused --retry 5 --retry-delay 0 -sf \ -X POST -H "Content-Type: application/json" \ --data-binary @data.json \ http://grafana:3000/api/dashboards/import ================================================ FILE: docker/examples/prometheus/prometheus.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # global: scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - job_name: 'zipkin' scrape_interval: 5s metrics_path: '/prometheus' static_configs: - targets: ['zipkin:9411'] ================================================ FILE: docker/start-zipkin ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts Zipkin set -eu STORAGE_TYPE=${STORAGE_TYPE:-mem} # Configure the Docker HEALTHCHECK export HEALTHCHECK_PORT=${QUERY_PORT:-9411} if [ "${STORAGE_TYPE}" = "mem" ]; then # When using in-memory provider, allocate 160m, most of which for trace storage JAVA_OPTS=${JAVA_OPTS:-"-Xms160m -Xmx160m -XX:+ExitOnOutOfMemoryError"} fi # MODULE_OPTS is not set in the zipkin-slim dist, so use it to detect if this is a slim build if [ -z "${MODULE_OPTS+x}" ]; then # Allocate less memory when using the slim build JAVA_OPTS=${JAVA_OPTS:-"-Xms64m -Xmx64m -XX:+ExitOnOutOfMemoryError"} # Use main class directly if there are no modules, as it measured 14% faster from JVM running to # available verses PropertiesLauncher when using Zipkin was based on Spring Boot 2.1 exec java ${JAVA_OPTS} -cp '.:BOOT-INF/lib/*:BOOT-INF/classes' zipkin.server.ZipkinServer "$@" else JAVA_OPTS=${JAVA_OPTS:-"-Xms96m -Xmx96m -XX:+ExitOnOutOfMemoryError"} # Disable Log4j2 JMX extensions when running the full build exec java ${MODULE_OPTS} ${JAVA_OPTS} -cp . \ -Dlog4j2.disable.jmx=true \ org.springframework.boot.loader.launch.PropertiesLauncher "$@" fi ================================================ FILE: docker/test-images/zipkin-activemq/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime layers of zipkin-activemq # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-activemq/start-activemq /docker-bin/ FROM ghcr.io/openzipkin/java:${java_version} as install WORKDIR /install # Use latest version from https://activemq.apache.org/components/classic/download/ ARG activemq_version=6.1.5 # Download the distribution RUN \ # Connection resets are frequent in GitHub Actions workflows \ wget --random-wait --tries=5 -qO- \ # Remove junk from the distribution while downloading it https://archive.apache.org/dist/activemq/${activemq_version}/apache-activemq-${activemq_version}-bin.tar.gz| tar xz \ --wildcards --exclude=examples --exclude=webapps-demo --strip=1 # Note: this uses the JDK image as ActiveMQ has a module dependency on JMX, # which isn't in our JRE. FROM ghcr.io/openzipkin/java:${java_version} as zipkin-activemq LABEL org.opencontainers.image.description="ActiveMQ Classic on OpenJDK and Alpine Linux" ARG activemq_version=6.1.5 LABEL activemq-version=$activemq_version # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-activemq"] # All content including binaries and logs write under WORKDIR ARG USER=activemq WORKDIR /${USER} ENV ACTIVEMQ_HOME=/${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} USER ${USER} # Copy binaries and config we installed earlier COPY --from=install --chown=${USER} /install . # Use to set heap, trust store or other system properties. ENV JAVA_OPTS="-Xms64m -Xmx64m -XX:+ExitOnOutOfMemoryError" EXPOSE 1883 5672 8161 61613 61614 61616 ================================================ FILE: docker/test-images/zipkin-activemq/README.md ================================================ ## zipkin-activemq Docker image The `zipkin-activemq` testing image runs ActiveMQ Classic for [ActiveMQ collector](../../../zipkin-collector/activemq) integration. To build `openzipkin/zipkin-activemq:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-activemq/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-activemq:test ``` You can use the env variable `JAVA_OPTS` to change settings such as heap size for ActiveMQ. ================================================ FILE: docker/test-images/zipkin-activemq/start-activemq ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts ActiveMQ # # This intentionally locates config using the current working directory, in order to consolidate # Dockerfile instructions to WORKDIR set -eu # Configure the Docker HEALTHCHECK # Configure the Docker HEALTHCHECK export HEALTHCHECK_PORT=61616 export HEALTHCHECK_KIND=tcp echo Starting ActiveMQ exec bin/activemq console ================================================ FILE: docker/test-images/zipkin-cassandra/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime layers of zipkin-cassandra # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-cassandra/start-cassandra /docker-bin/ COPY docker/test-images/zipkin-cassandra/install.sh /install/ COPY zipkin-storage/cassandra/src/main/resources/*.cql /zipkin-schemas/ FROM ghcr.io/openzipkin/java:${java_version} as install # Use latest stable version: https://cassandra.apache.org/download/ # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG cassandra_version=4.1.8 ENV CASSANDRA_VERSION=$cassandra_version WORKDIR /install COPY --from=scratch /zipkin-schemas/* ./zipkin-schemas/ COPY --from=scratch /install/install.sh /tmp/ RUN /tmp/install.sh && rm /tmp/install.sh FROM ghcr.io/openzipkin/java:${java_version}-jre as zipkin-cassandra LABEL org.opencontainers.image.description="Cassandra on OpenJDK and Alpine Linux with Zipkin keyspaces pre-installed" ARG cassandra_version=4.1.8 LABEL cassandra-version=$cassandra_version ENV CASSANDRA_VERSION=$cassandra_version # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-cassandra"] # All content including binaries and logs write under WORKDIR ARG USER=cassandra WORKDIR /${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} USER ${USER} # Copy binaries and config we installed earlier COPY --from=install --chown=${USER} /install . # Set variables Cassandra's start script wants ENV JAVA_OPTS="-Xms256m -Xmx256m -XX:+ExitOnOutOfMemoryError -Djava.net.preferIPv4Stack=true" ENV LOGGING_LEVEL=WARN EXPOSE 9042 ================================================ FILE: docker/test-images/zipkin-cassandra/README.md ================================================ ## zipkin-cassandra Docker image The `zipkin-cassandra` testing image runs Cassandra 4.x initialized with Zipkin's schema for [Cassandra storage](../../../zipkin-storage/cassandra) integration. Besides norms defined in [docker-java](https://github.com/openzipkin/docker-java), this accepts the following environment variables: * `LOGGING_LEVEL`: Root Log4J logging level sent to stdout. Defaults to "WARN" To build `openzipkin/zipkin-cassandra:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-cassandra/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-cassandra:test ``` ================================================ FILE: docker/test-images/zipkin-cassandra/install.sh ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # install script used only in building the docker image, but not at runtime. # This uses relative path so that you can change the home dir without editing this file. # This also trims dependencies to only those used at runtime. set -eux echo "*** Installing Cassandra" # Create directories for the Java classpath mkdir classes lib cat > pom.xml <<-'EOF' 4.0.0 io.zipkin.cassandra get-cassandra 0.1.0-SNAPSHOT pom org.apache.cassandra cassandra-all ${cassandra.version} net.java.dev.jna * com.github.jbellis * org.slf4j * ch.qos.logback * net.java.dev.jna jna 5.16.0 com.github.jbellis jamm 0.4.0 org.slf4j slf4j-log4j12 1.7.36 EOF mvn -q --batch-mode -DoutputDirectory=lib \ -Dcassandra.version=${CASSANDRA_VERSION} \ org.apache.maven.plugins:maven-dependency-plugin:3.8.1:copy-dependencies rm pom.xml # Get a version of ObjectSizes.java that compiles with jamm 0.4.0 wget --random-wait --tries=5 -qO ObjectSizes.java \ https://raw.githubusercontent.com/apache/cassandra/refs/tags/cassandra-5.0.3/src/java/org/apache/cassandra/utils/ObjectSizes.java # Rename a public method back to the same name as used in Cassandra 4.1. sed -i 's/sizeOnHeapExcludingDataOf/sizeOnHeapExcludingData/g' ObjectSizes.java # Compile it into classes, which overrides the same class from Cassandra 4.1. javac -cp 'lib/*' -d classes ObjectSizes.java # Make sure you use relative paths in references like this, so that installation # is decoupled from runtime mkdir -p conf data commitlog saved_caches hints triggers # Generate basic required configuration from default values cat > conf/cassandra.yaml <<-'EOF' partitioner: org.apache.cassandra.dht.Murmur3Partitioner commitlog_sync: periodic commitlog_sync_period_in_ms: 10000 endpoint_snitch: SimpleSnitch # override via -Dcassandra.storage_port=7000 storage_port: 7000 # override via -Dcassandra.native_transport_port=9042 native_transport_port: 9042 listen_address: 127.0.0.1 start_native_transport: true seed_provider: - class_name: org.apache.cassandra.locator.SimpleSeedProvider parameters: - seeds: "127.0.0.1" # Disabled by default in Cassandra 4 enable_sasi_indexes: true EOF temp_storage_port=7010 temp_native_transport_port=9052 # Keep INFO logs as if this fails in CI, we'll get more insight. These aren't displayed unless we # have a crash. cat > conf/log4j.properties <<-'EOF' log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n log4j.appender.stdout.Target=System.out EOF cat zipkin-schemas/zipkin2-schema.cql zipkin-schemas/zipkin2-schema-indexes.cql > schema # read_repair_chance options were removed and make Cassandra crash starting in v4 # See https://cassandra.apache.org/doc/latest/operating/read_repair.html#background-read-repair sed -i '/read_repair_chance/d' schema # Run cassandra on a different port temporarily in order to setup the schema. # # We also add exports and opens from both Cassandra v4 and v5, except for # attach, compiler and rmi because our JRE excludes these modules. # # Merging makes adding Cassandra v5 easier and lets us share a common JRE 17+ # with other test images even if Cassandra v4 will never officially support it. # https://github.com/apache/cassandra/blob/cassandra-4.1.8/conf/jvm11-server.options # https://github.com/apache/cassandra/blob/cassandra-5.0.3/conf/jvm17-server.options # # Finally, we allow security manager to prevent JRE 21 crashing when Cassandra # attempts ThreadAwareSecurityManager.install() java -cp 'classes:lib/*' -Xms64m -Xmx64m -XX:+ExitOnOutOfMemoryError -verbose:gc \ -Djava.security.manager=allow \ -Djdk.attach.allowAttachSelf=true \ --add-exports java.base/jdk.internal.misc=ALL-UNNAMED \ --add-exports java.base/jdk.internal.ref=ALL-UNNAMED \ --add-exports java.base/sun.nio.ch=ALL-UNNAMED \ --add-exports java.sql/java.sql=ALL-UNNAMED \ --add-exports java.base/java.lang.ref=ALL-UNNAMED \ --add-exports jdk.unsupported/sun.misc=ALL-UNNAMED \ --add-opens java.base/java.lang.module=ALL-UNNAMED \ --add-opens java.base/jdk.internal.loader=ALL-UNNAMED \ --add-opens java.base/jdk.internal.ref=ALL-UNNAMED \ --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED \ --add-opens java.base/jdk.internal.math=ALL-UNNAMED \ --add-opens java.base/jdk.internal.module=ALL-UNNAMED \ --add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED \ --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED \ --add-opens java.base/java.io=ALL-UNNAMED \ --add-opens java.base/java.nio=ALL-UNNAMED \ --add-opens java.base/sun.nio.ch=ALL-UNNAMED \ --add-opens java.base/java.io=ALL-UNNAMED \ --add-opens java.base/java.lang.reflect=ALL-UNNAMED \ --add-opens java.base/java.lang=ALL-UNNAMED \ --add-opens java.base/java.util=ALL-UNNAMED \ --add-opens java.base/java.nio=ALL-UNNAMED \ --add-opens java.base/java.util.concurrent=ALL-UNNAMED \ --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED \ -Dcassandra.storage_port=${temp_storage_port} \ -Dcassandra.native_transport_port=${temp_native_transport_port} \ -Dcassandra.storagedir=${PWD} \ -Dcassandra.triggers_dir=${PWD}/triggers \ -Dcassandra.config=file:${PWD}/conf/cassandra.yaml \ -Dlog4j.configuration=file:${PWD}/conf/log4j.properties \ org.apache.cassandra.service.CassandraDaemon > temp_cassandra.out 2>&1 & temp_cassandra_pid=$! is_cassandra_alive() { if ! kill -0 ${temp_cassandra_pid}; then cat temp_cassandra.out maybe_crash_file=hs_err_pid${temp_cassandra_pid}.log test -f $maybe_crash_file && cat $maybe_crash_file return 1 fi return 0 } is_cassandra_alive || exit 1 echo "*** Installing cqlsh" # cqlsh 4.x is not compatible with Python 3.12 by default. # See https://issues.apache.org/jira/browse/CASSANDRA-19206 python3_version=3.12 apk add --update --no-cache python3=~${python3_version} # Installing cqlsh requires cffi package. Normally this doesn't need # to be compiled, but something isn't right with aarch64 when installing # cqlsh it needs to build cffi. To unblock support for aarch64, adding # the following are necessary for compiling cffi. If pip someday changes and # doesn't compile cffi on aarch64 then we can remove these dependencies. # libev is required when using python 3.12 # TODO: remove git when https://github.com/jeffwidman/cqlsh/pull/37 is released apk add --update --no-cache gcc python3-dev=~${python3_version} musl-dev libffi-dev libev libev-dev git # PEP 668 protects against mixing system and pip packages. Setup virtual env to avoid this. python3 -m venv .venv . .venv/bin/activate python3 -m ensurepip --upgrade pip install --upgrade pip setuptools wheel # Installing from trunk since released versions have dependency on old setup tools pip install -Iq git+https://github.com/apache/cassandra-python-driver@trunk pip install cqlsh cql() { cqlsh "$@" 127.0.0.1 ${temp_native_transport_port} } # Excessively long timeout to avoid having to create an ENV variable, decide its name, etc. timeout=180 echo "Will wait up to ${timeout} seconds for Cassandra to come up before installing Schema" while [ "$timeout" -gt 0 ] && ! cql -e 'SHOW VERSION' > /dev/null 2>&1; do is_cassandra_alive || exit 1 sleep 1 timeout=$(($timeout - 1)) done echo "*** Importing Scheme" cat schema | cql --debug && rm schema echo "*** Stopping Cassandra" kill ${temp_cassandra_pid} wait # The image will use a less chatty Log4J conf which only logs warnings (to stdout). cat > conf/log4j.properties <<-'EOF' log4j.rootLogger=WARN, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n log4j.appender.stdout.Target=System.out # Ignore that we are using log4j as we aren't starting JMX anyway log4j.logger.org.apache.cassandra.utils.logging=ERROR # Ignore that we cannot increase RLIMIT_MEMLOCK or run Cassandra as root log4j.logger.org.apache.cassandra.utils.NativeLibrary=ERROR # Ignore that we disabled JMX and haven't installed jemalloc (not available on Alpine) log4j.logger.org.apache.cassandra.service.StartupChecks=OFF # Ignore C* 3.x java.lang.NoSuchMethodError: 'sun.misc.Cleaner sun.nio.ch.DirectBuffer.cleaner()' log4j.logger.org.apache.cassandra.io.util.FileUtils=OFF # Ignore warnings about less than 64GB disk log4j.logger.org.apache.cassandra.config.DatabaseDescriptor=ERROR EOF # Take a backup so that we can safely mount an empty volume over the data directory and maintain the schema cp -R data/ data-backup/ echo "*** Cleaning Up" rm -rf zipkin-schemas/ temp_cassandra.out echo "*** Image build complete" ================================================ FILE: docker/test-images/zipkin-cassandra/start-cassandra ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts Cassandra # # This intentionally locates config using the current working directory, in order to consolidate # Dockerfile instructions to WORKDIR set -eu # Apply one-time deferred configuration that relies on ENV variables # # If the schema has been removed due to mounting, restore from our backup. see: install if [ ! -d data/zipkin2 ]; then cp -rf data-backup/* data/ fi IP="$(hostname -i || echo '127.0.0.1')" sed -i "s/127.0.0.1/${IP}/g" conf/cassandra.yaml # Replace the logging level sed -i "s/log4j.rootLogger.*/log4j.rootLogger=${LOGGING_LEVEL}, stdout/" conf/log4j.properties # Use agent to allow instrumentation of a lambda: CASSANDRA-16304 JAMM_JAR=$(ls lib/jamm-*.jar) # Configure the Docker HEALTHCHECK export HEALTHCHECK_IP=${IP} export HEALTHCHECK_PORT=9042 export HEALTHCHECK_KIND=tcp echo Starting Cassandra # -cp 'classes:lib/*' allows layers to patch the image without packaging or # overwriting jars. # # We also add exports and opens from both Cassandra v4 and v5, except for # attach, compiler and rmi because our JRE excludes these modules. # # Merging makes adding Cassandra v5 easier and lets us share a common JRE 17+ # with other test images even if Cassandra v4 will never officially support it. # https://github.com/apache/cassandra/blob/cassandra-4.0.11/conf/jvm11-server.options # https://github.com/apache/cassandra/blob/cassandra-5.0/conf/jvm17-server.options # # Finally, we allow security manager to prevent JRE 21 crashing when Cassandra # attempts ThreadAwareSecurityManager.install() exec java -cp 'classes:lib/*' ${JAVA_OPTS} \ -Djava.security.manager=allow \ -Xbootclasspath/a:${JAMM_JAR} -javaagent:${JAMM_JAR} \ -Djdk.attach.allowAttachSelf=true \ --add-exports java.base/jdk.internal.misc=ALL-UNNAMED \ --add-exports java.base/jdk.internal.ref=ALL-UNNAMED \ --add-exports java.base/sun.nio.ch=ALL-UNNAMED \ --add-exports java.sql/java.sql=ALL-UNNAMED \ --add-exports java.base/java.lang.ref=ALL-UNNAMED \ --add-exports jdk.unsupported/sun.misc=ALL-UNNAMED \ --add-opens java.base/java.lang.module=ALL-UNNAMED \ --add-opens java.base/jdk.internal.loader=ALL-UNNAMED \ --add-opens java.base/jdk.internal.ref=ALL-UNNAMED \ --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED \ --add-opens java.base/jdk.internal.math=ALL-UNNAMED \ --add-opens java.base/jdk.internal.module=ALL-UNNAMED \ --add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED \ --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED \ --add-opens java.base/java.io=ALL-UNNAMED \ --add-opens java.base/java.nio=ALL-UNNAMED \ --add-opens java.base/sun.nio.ch=ALL-UNNAMED \ --add-opens java.base/java.io=ALL-UNNAMED \ --add-opens java.base/java.lang.reflect=ALL-UNNAMED \ --add-opens java.base/java.lang=ALL-UNNAMED \ --add-opens java.base/java.util=ALL-UNNAMED \ --add-opens java.base/java.nio=ALL-UNNAMED \ --add-opens java.base/java.util.concurrent=ALL-UNNAMED \ --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED \ -Djava.io.tmpdir=/tmp \ -Dcassandra-foreground=yes \ -Dcassandra.storagedir=${PWD} \ -Dcassandra.triggers_dir=${PWD}/triggers \ -Dcassandra.config=file:${PWD}/conf/cassandra.yaml \ -Dlog4j.configuration=file:${PWD}/conf/log4j.properties \ org.apache.cassandra.service.CassandraDaemon "$@" ================================================ FILE: docker/test-images/zipkin-elasticsearch7/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime layers of zipkin-elasticsearch7 # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-elasticsearch7/start-elasticsearch /docker-bin/ COPY docker/test-images/zipkin-elasticsearch7/config/ /config/ FROM ghcr.io/openzipkin/java:${java_version} as install WORKDIR /install # Use latest 7.x version from https://www.elastic.co/downloads/past-releases#elasticsearch-no-jdk # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG elasticsearch7_version=7.17.27 # Download only the OSS distribution (lacks X-Pack) RUN \ # Connection resets are frequent in GitHub Actions workflows \ wget --random-wait --tries=5 -qO- \ # We don't download bin scripts as we customize for reasons including BusyBox problems https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${elasticsearch7_version}-no-jdk-linux-x86_64.tar.gz| tar xz \ --wildcards --strip=1 --exclude=*/bin && mkdir classes COPY --from=scratch /config/ ./config/ # Use a full Java distribution rather than adding test modules to the # production -jre base layer used by zipkin and zipkin-slim. FROM ghcr.io/openzipkin/java:${java_version} as zipkin-elasticsearch7 LABEL org.opencontainers.image.description="Elasticsearch distribution on OpenJDK and Alpine Linux" ARG elasticsearch7_version=7.17.27 LABEL elasticsearch-version=$elasticsearch7_version # The full license is also included in the image at /elasticsearch/LICENSE.txt. LABEL org.opencontainers.image.licenses="Elastic-License-2.0" # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-elasticsearch"] # All content including binaries and logs write under WORKDIR ARG USER=elasticsearch WORKDIR /${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} USER ${USER} # Copy binaries and config we installed earlier COPY --from=install --chown=${USER} /install . # Use to set heap, trust store or other system properties. ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:+ExitOnOutOfMemoryError" ENV LIBFFI_TMPDIR=/tmp EXPOSE 9200 ================================================ FILE: docker/test-images/zipkin-elasticsearch7/README.md ================================================ ## zipkin-elasticsearch7 Docker image The `zipkin-elasticsearch7` testing image runs Elasticsearch 7.x for [Elasticsearch storage](../../../zipkin-storage/elasticsearch) integration. To build `openzipkin/zipkin-elasticsearch7:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-elasticsearch7/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-elasticsearch7:test ``` You can use the env variable `JAVA_OPTS` to change settings such as heap size for Elasticsearch. #### Host setup Elasticsearch is [strict](https://github.com/docker-library/docs/tree/master/elasticsearch#host-setup) about virtual memory. You will need to adjust accordingly (especially if you notice Elasticsearch crash!) ```bash # If docker is running on your host machine, adjust the kernel setting directly $ sudo sysctl -w vm.max_map_count=262144 # If using docker-machine/Docker Toolbox/Boot2Docker, remotely adjust the same $ docker-machine ssh default "sudo sysctl -w vm.max_map_count=262144" # If using colima, it is similar as well $ colima ssh "sudo sysctl -w vm.max_map_count=262144" ``` #### License This Elasticsearch image is only made for testing features supported by Zipkin, and is subject to [Elastic-License-2.0](https://www.elastic.co/licensing/elastic-license). For more details, inspect the LICENSE.txt and NOTICE.txt in the /elasticsearch directory of this image. ================================================ FILE: docker/test-images/zipkin-elasticsearch7/config/elasticsearch.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # cluster.name: "docker-cluster" network.host: 0.0.0.0 node.name: zipkin-elasticsearch # Enable development mode and disable bootstrap checks # See https://www.elastic.co/guide/en/elasticsearch/reference/current/bootstrap-checks.html discovery.type: single-node # Avoid deprecation errors: as of 8.x the only accepted value is true. cluster.routing.allocation.disk.watermark.enable_for_single_data_node: true # This is a test image, so we are not afraid to delete all indices. Avoids: # Wildcard expressions or all indices are not allowed action.destructive_requires_name: false # We don't use geoip ingest.geoip.downloader.enabled: false # disable all xpack features than can be disabled xpack.graph.enabled: false xpack.ml.enabled: false xpack.security.enabled: false xpack.watcher.enabled: false ================================================ FILE: docker/test-images/zipkin-elasticsearch7/config/log4j2.properties ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # status = error appender.console.type = Console appender.console.name = console appender.console.layout.type = PatternLayout appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] [%node_name]%marker %m%n rootLogger.level = info rootLogger.appenderRef.console.ref = console # unsolved https://github.com/sherifabdlnaby/elastdocker/issues/108 logger.aws.name = com.amazonaws.auth.profile.internal.BasicProfileConfigFileLoader logger.aws.level = error ================================================ FILE: docker/test-images/zipkin-elasticsearch7/start-elasticsearch ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts Elasticsearch # # This intentionally locates config using the current working directory, in order to consolidate # Dockerfile instructions to WORKDIR set -eu # This file inlines what's done by the Elasticsearch script machinery, which doesn't work here due # to our images using busybox (not bash). See #3044 # # Notable settings: # * lower heap size # * tmpdir manual as https://github.com/elastic/elasticsearch/pull/31003 was closed won't fix # * disable log4j JMX not just because we don't use it... # * ES enables security manager https://github.com/elastic/elasticsearch/issues/21932#issuecomment-264435034 # Configure the Docker HEALTHCHECK export HEALTHCHECK_PORT=9200 export HEALTHCHECK_PATH=/_cluster/health # -cp 'classes:lib/*' allows layers to patch the image without packaging or overwriting jars # We allow security manager (via flag to prevent JRE 21 crash) as Elasticsearch.main needs it. exec java -cp 'classes:lib/*' ${JAVA_OPTS} \ -Djava.security.manager=allow \ -Djava.io.tmpdir=/tmp \ -Dlog4j2.disable.jmx=true \ -Des.path.home=$PWD -Des.path.conf=$PWD/config \ org.elasticsearch.bootstrap.Elasticsearch "$@" ================================================ FILE: docker/test-images/zipkin-elasticsearch8/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime layers of zipkin-elasticsearch8 # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-elasticsearch8/start-elasticsearch /docker-bin/ COPY docker/test-images/zipkin-elasticsearch7/config/ /config/ FROM ghcr.io/openzipkin/java:${java_version} as install WORKDIR /install # Use latest 8.x version from https://www.elastic.co/downloads/past-releases#elasticsearch # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG elasticsearch8_version=8.17.2 # Download only the OSS distribution (lacks X-Pack) RUN \ # Connection resets are frequent in GitHub Actions workflows \ wget --random-wait --tries=5 -qO- \ # We don't download bin scripts as we customize for reasons including BusyBox problems https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${elasticsearch8_version}-linux-x86_64.tar.gz| tar xz \ --wildcards --strip=1 --exclude=jdk --exclude=*/bin COPY --from=scratch /config/ ./config/ # Use a full Java distribution rather than adding test modules to the # production -jre base layer used by zipkin and zipkin-slim. FROM ghcr.io/openzipkin/java:${java_version} as zipkin-elasticsearch8 LABEL org.opencontainers.image.description="Elasticsearch distribution on OpenJDK and Alpine Linux" ARG elasticsearch8_version=8.17.2 LABEL elasticsearch-version=$elasticsearch8_version # The full license is also included in the image at /elasticsearch/LICENSE.txt. LABEL org.opencontainers.image.licenses="Elastic-License-2.0" # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-elasticsearch"] # All content including binaries and logs write under WORKDIR ARG USER=elasticsearch WORKDIR /${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} USER ${USER} # Copy binaries and config we installed earlier COPY --from=install --chown=${USER} /install . # Use to set heap, trust store or other system properties. ENV ES_JAVA_OPTS="-Xms512m -Xmx512m -XX:+ExitOnOutOfMemoryError" ENV LIBFFI_TMPDIR=/tmp EXPOSE 9200 ================================================ FILE: docker/test-images/zipkin-elasticsearch8/README.md ================================================ ## zipkin-elasticsearch8 Docker image The `zipkin-elasticsearch8` testing image runs Elasticsearch 8.x for [Elasticsearch storage](../../../zipkin-storage/elasticsearch) integration. To build `openzipkin/zipkin-elasticsearch8:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-elasticsearch8/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-elasticsearch8:test ``` You can use the env variable `ES_JAVA_OPTS` to change settings such as heap size for Elasticsearch. #### Host setup Elasticsearch is [strict](https://github.com/docker-library/docs/tree/master/elasticsearch#host-setup) about virtual memory. You will need to adjust accordingly (especially if you notice Elasticsearch crash!) ```bash # If docker is running on your host machine, adjust the kernel setting directly $ sudo sysctl -w vm.max_map_count=262144 # If using docker-machine/Docker Toolbox/Boot2Docker, remotely adjust the same $ docker-machine ssh default "sudo sysctl -w vm.max_map_count=262144" # If using colima, it is similar as well $ colima ssh "sudo sysctl -w vm.max_map_count=262144" ``` #### License This Elasticsearch image is only made for testing features supported by Zipkin, and is subject to [Elastic-License-2.0](https://www.elastic.co/licensing/elastic-license). For more details, inspect the LICENSE.txt and NOTICE.txt in the /elasticsearch directory of this image. ================================================ FILE: docker/test-images/zipkin-elasticsearch8/start-elasticsearch ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts Elasticsearch # # This intentionally locates config using the current working directory, in order to consolidate # Dockerfile instructions to WORKDIR set -eu # Configure the Docker HEALTHCHECK export HEALTHCHECK_PORT=9200 export HEALTHCHECK_PATH=/_cluster/health # This loads the ES launcher because configuration of the actual process is # in binary and not documented for external use. # See https://github.com/elastic/elasticsearch/blob/v8.11.3/server/src/main/java/org/elasticsearch/bootstrap/ServerArgs.java#L57 # # Notably, this means that just like the default image, the ES daemon is not # pid 1 exec java -cp 'lib/*:lib/cli-launcher/*' -XX:+UseSerialGC \ -Dcli.name=server \ -Dcli.script=$PWD/bin/elasticsearch \ -Dcli.libs=lib/tools/server-cli \ -Des.path.home=$PWD \ -Des.path.conf=$PWD/config \ -Des.distribution.type=docker \ org.elasticsearch.launcher.CliToolLauncher ================================================ FILE: docker/test-images/zipkin-eureka/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime base layers of eureka and eureka-slim. # # Use latest version here: https://github.com/orgs/openeureka/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-eureka/start-eureka /docker-bin/ # We needsource because as of Eureka 2.0, there is no distribution in the # source repository anymore, and the war isn't functional either. The current # approach is to build your own with Spring Cloud. # # See https://github.com/Netflix/eureka/releases/tag/v2.0.0 COPY build-bin/maven /code/ COPY docker/test-images/zipkin-eureka /code/ # This version is only used during the install process. Try to be consistent as it reduces layers, # which reduces downloads. FROM ghcr.io/openzipkin/java:${java_version} as install WORKDIR /code COPY --from=scratch /code/ . # Build the custom Eureka server RUN /code/maven_build && \ mvn -q --batch-mode -DoutputDirectory=/install/lib dependency:copy-dependencies && \ cp -r target/classes /install/ # Use a full Java distribution rather than adding test modules to the # production -jre base layer used by zipkin and zipkin-slim. # # Specifically, this is about NoClassDefFoundError: org/ietf/jgss/GSSException FROM ghcr.io/openzipkin/java:${java_version} as zipkin-eureka LABEL org.opencontainers.image.description="Eureka on OpenJDK and Alpine Linux" # All content including binaries and logs write under WORKDIR ARG USER=eureka WORKDIR /${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # Copy binaries and config we installed earlier COPY --from=install --chown=${USER} /install . # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENV HEALTHCHECK_PATH=/actuator/health ENV HEALTHCHECK_PORT=8761 ENTRYPOINT ["start-eureka"] # Switch to the runtime user USER ${USER} # Use to set heap, trust store or other system properties. ENV JAVA_OPTS="-Xms64m -Xmx64m -XX:+ExitOnOutOfMemoryError" EXPOSE 8761 ================================================ FILE: docker/test-images/zipkin-eureka/README.md ================================================ ## zipkin-eureka Docker image The `zipkin-eureka` testing image runs Eureka Server for service discovery integration of the Zipkin server. This listens on port 8761. Besides norms defined in [docker-java](https://github.com/openzipkin/docker-java), this accepts the following environment variables: * `EUREKA_USERNAME`: username for authenticating endpoints under "/eureka". * `EUREKA_PASSWORD`: password for authenticating endpoints under "/eureka". * `JAVA_OPTS`: to change settings such as heap size for Eureka. To build `openzipkin/zipkin-eureka:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-eureka/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-eureka:test $ docker run -p 8761:8761 --rm openzipkin/zipkin-eureka:test ``` ================================================ FILE: docker/test-images/zipkin-eureka/pom.xml ================================================ 4.0.0 io.zipkin.test eureka 1.0-SNAPSHOT jar Netflix Eureka test binary Netflix Eureka test binary 17 17 17 com.fasterxml.jackson jackson-bom 2.21.1 pom import org.springframework.boot spring-boot-dependencies 3.5.11 pom import com.google.guava guava 33.4.0-jre com.thoughtworks.xstream xstream 1.4.21 org.apache.httpcomponents httpclient 4.5.14 org.springframework.cloud spring-cloud-starter-netflix-eureka-server 4.3.1 commons-logging commons-logging software.amazon.ion ion-java com.amazon.ion ion-java 1.11.10 org.springframework.boot spring-boot-starter-cache com.github.ben-manes.caffeine caffeine ================================================ FILE: docker/test-images/zipkin-eureka/src/main/java/zipkin/test/EurekaProperties.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin.test; import org.springframework.boot.context.properties.ConfigurationProperties; /** Properties for configuring and building a {@link EurekaServer}. */ @ConfigurationProperties("eureka") class EurekaProperties { /** Optional username to require. */ private String username; /** Optional password to require. */ private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ================================================ FILE: docker/test-images/zipkin-eureka/src/main/java/zipkin/test/EurekaSecurity.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin.test; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Base64; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.OncePerRequestFilter; import static java.nio.charset.StandardCharsets.UTF_8; /** This enables security, particularly only BASIC auth, when {@code EUREKA_USERNAME} is set. */ @Configuration @ConditionalOnProperty("eureka.username") @EnableConfigurationProperties(EurekaProperties.class) public class EurekaSecurity { @Bean FilterRegistrationBean authFilter(EurekaProperties props) { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new BasicAuthFilter(props.getUsername(), props.getPassword())); registrationBean.addUrlPatterns("/eureka/*"); // Auth /eureka, though only v2 is valid registrationBean.setOrder(2); return registrationBean; } /** Implements BASIC instead of spring-security + CORS, CSRF and management exclusions. */ static final class BasicAuthFilter extends OncePerRequestFilter { final String expectedAuthorization; BasicAuthFilter(String username, String password) { expectedAuthorization = "Basic " + Base64.getEncoder().encodeToString((username + ':' + password).getBytes(UTF_8)); } @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException { String authHeader = req.getHeader("Authorization"); if (expectedAuthorization.equals(authHeader)) { chain.doFilter(req, res); // Pass on the supplied credentials return; } res.setHeader("WWW-Authenticate", "Basic realm=\"Realm'\""); res.sendError(HttpServletResponse.SC_UNAUTHORIZED); // Return 401 otherwise. } } } ================================================ FILE: docker/test-images/zipkin-eureka/src/main/java/zipkin/test/EurekaServer.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; import org.springframework.context.annotation.Import; /** * This disables automatic security configuration, deferring to {@linkplain EurekaSecurity}. * Doing so allows Eureka to start as if spring-security wasn't in the classpath. */ @SpringBootApplication( exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class} ) @EnableEurekaServer @Import(EurekaSecurity.class) public class EurekaServer { public static void main(String[] args) { SpringApplication.run(EurekaServer.class, args); } } ================================================ FILE: docker/test-images/zipkin-eureka/src/main/resources/application.yaml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Add configuration to disable as much server caching as possible. Note that # not all configuration here are defined by netflix/eureka, rather some are # specific to spring-cloud/spring-cloud-netflix. eureka: client: registerWithEureka: false fetchRegistry: false # in netflix/eureka this is shouldFetchRegistry registryFetchIntervalSeconds: 1 server: useReadOnlyResponseCache: false # We could set myUrl to avoid this server thinking it is also an # unavailable replica. However, the effect of doing so is worse. # See https://github.com/spring-cloud/spring-cloud-netflix/issues/4251 server: port: 8761 spring: jmx: # reduce startup time by excluding unexposed JMX service enabled: false main: banner-mode: "off" profiles: active: "default" logging: level: # reduce chattiness root: 'WARN' # hush initialization warnings from BeanPostProcessorChecker org.springframework.context.support: 'OFF' # show startup completion zipkin.test.EurekaServer: 'INFO' ================================================ FILE: docker/test-images/zipkin-eureka/start-eureka ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts Eureka set -eu exec java -cp 'classes:lib/*' ${JAVA_OPTS} zipkin.test.EurekaServer "$@" ================================================ FILE: docker/test-images/zipkin-kafka/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime layers of zipkin-kafka # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-kafka/start-kafka-zookeeper /docker-bin/ COPY docker/test-images/zipkin-kafka/install.sh /install/ FROM ghcr.io/openzipkin/java:${java_version} as install # Use latest release from: https://kafka.apache.org/downloads # # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG kafka_version=3.9.1 ENV KAFKA_VERSION=$kafka_version # Note: Scala 2.13+ supports JRE 14 ARG scala_version=2.13 ENV SCALA_VERSION=$scala_version WORKDIR /install COPY --from=scratch /install/install.sh /tmp/ RUN /tmp/install.sh && rm /tmp/install.sh # Share the same base image to reduce layers used in testing FROM ghcr.io/openzipkin/java:${java_version}-jre as zipkin-kafka LABEL org.opencontainers.image.description="Kafka and ZooKeeper on OpenJDK and Alpine Linux" ARG kafka_version=3.9.0 LABEL kafka-version=$kafka_version # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-kafka-zookeeper"] # All content including binaries and logs write under WORKDIR ARG USER=kafka WORKDIR /${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} USER ${USER} # Copy binaries and config we installed earlier COPY --from=install --chown=${USER} /install . # ${KAFKA_ADVERTISED_HOST_NAME}:19092 is for connections from the Docker host ENV KAFKA_ADVERTISED_HOST_NAME=localhost # Use to set heap, trust store or other system properties. ENV ZOOKEEPER_JAVA_OPTS="-Xms32m -Xmx32m -XX:+ExitOnOutOfMemoryError" ENV JAVA_OPTS="-Xms256m -Xmx256m -XX:+ExitOnOutOfMemoryError" ENV LOGGING_LEVEL=WARN EXPOSE 2181 9092 19092 ================================================ FILE: docker/test-images/zipkin-kafka/README.md ================================================ ## zipkin-kafka Docker image The `zipkin-kafka` testing image runs both Kafka+ZooKeeper for the [Kafka collector](../../../zipkin-collector/kafka) and the upcoming [Kafka storage](https://github.com/openzipkin-contrib/zipkin-storage-kafka). Besides norms defined in [docker-java](https://github.com/openzipkin/docker-java), this accepts the following environment variables: * `LOGGING_LEVEL`: Root Log4J logging level sent to stdout. Defaults to "WARN" To build `openzipkin/zipkin-kafka:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-kafka/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-kafka:test ``` Then configure the [Kafka sender](https://github.com/openzipkin/zipkin-reporter-java/blob/master/kafka/src/main/java/zipkin2/reporter/kafka/KafkaSender.java) using a `bootstrapServers` value of `host.docker.internal:9092` if your application is inside the same docker network or `localhost:19092` if not, but running on the same host. In other words, if you are running a sample application on your laptop, you would use `localhost:19092` bootstrap server to send spans to the Kafka broker running in Docker. ================================================ FILE: docker/test-images/zipkin-kafka/install.sh ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # install script used only in building the docker image, but not at runtime. # This uses relative path so that you can change the home dir without editing this file. # This also trims dependencies to only those used at runtime. set -eux echo "*** Installing Kafka and dependencies" # Create directories for the Java classpath mkdir classes lib # Dist includes large dependencies needed by streams and connect: retain only broker and ZK. # We can do this because broker is independent from both kafka-streams and connect modules. # See KAFKA-10380 # # TODO: MDEP-723 if addressed can remove the pom.xml here cat > pom.xml <<-'EOF' 4.0.0 io.zipkin.kafka get-kafka 0.1.0-SNAPSHOT pom org.apache.kafka kafka_${scala.version} ${kafka.version} org.slf4j slf4j-log4j12 1.7.36 EOF mvn -q --batch-mode -DoutputDirectory=lib \ -Dscala.version=${SCALA_VERSION} -Dkafka.version=${KAFKA_VERSION} \ org.apache.maven.plugins:maven-dependency-plugin:3.8.1:copy-dependencies rm pom.xml # Make sure you use relative paths in references like this, so that installation # is decoupled from runtime mkdir -p bin config data/kafka data/zookeeper # Make a basic log4j config which only logs warnings (to stdout) # # NOTE: Two unavoidable log WARN messages remain: # 1. Either no config or no quorum defined in config, running in standalone mode (org.apache.zookeeper.server.quorum.QuorumPeerMain) # * https://github.com/apache/zookeeper/blob/e91455c1e3c50405666cd8afad71d99dceb7b340/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java#L138-L140 # 2. No meta.properties file under dir /kafka/./data/kafka/meta.properties (kafka.server.BrokerMetadataCheckpoint) # * meta.properties file is generated when broker joins the cluster, using an auto-generated cluster id: cat > config/log4j.properties <<-'EOF' log4j.rootLogger=WARN, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n log4j.appender.stdout.Target=System.out EOF # Set explicit, basic configuration cat > config/zookeeper.properties <<-'EOF' dataDir=./data/zookeeper clientPort=2181 maxClientCnxns=0 admin.enableServer=false # allow ruok command for testing ZK health 4lw.commands.whitelist=srvr,ruok admin.enableServer=false EOF cat > config/server.properties <<-'EOF' broker.id=0 zookeeper.connect=127.0.0.1:2181 replica.socket.timeout.ms=1500 # log.dirs is about Kafka's data not Log4J log.dirs=./data/kafka auto.create.topics.enable=true offsets.topic.replication.factor=1 listeners=PLAINTEXT://0.0.0.0:9092,PLAINTEXT_HOST://0.0.0.0:19092 listener.security.protocol.map=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT EOF # Make a basic script for launching Kafka commands cat > bin/kafka-run-class.sh <<-'EOF' #!/bin/sh set -eu # classes allows layers to patch the image without packaging or overwriting jars exec java -cp 'classes:lib/*' ${JAVA_OPTS} \ -Djava.io.tmpdir=/tmp \ -Dlog4j.configuration=file:./config/log4j.properties \ "$@" EOF chmod 755 bin/kafka-run-class.sh echo "*** Image build complete" ================================================ FILE: docker/test-images/zipkin-kafka/start-kafka-zookeeper ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts ZooKeeper and then Kafka # # This intentionally locates config using the current working directory, in order to consolidate # Dockerfile instructions to WORKDIR set -eu # Apply one-time deferred configuration that relies on ENV variables # # Internal docker producers and consumers use the normal hostname:9092, and outside docker the advertised host on port 19092 ADVERTISED_LISTENERS="advertised.listeners=PLAINTEXT://${HOSTNAME}:9092,PLAINTEXT_HOST://${KAFKA_ADVERTISED_HOST_NAME}:19092" KAFKA_CONFIG=./config/server.properties grep -qF -- "$ADVERTISED_LISTENERS" $KAFKA_CONFIG || echo "$ADVERTISED_LISTENERS" >> $KAFKA_CONFIG # Replace the logging level sed -i "s/log4j.rootLogger.*/log4j.rootLogger=${LOGGING_LEVEL}, stdout/" config/log4j.properties echo Starting ZooKeeper # -cp 'classes:lib/*' allows layers to patch the image without packaging or overwriting jars exec java -cp 'classes:lib/*' ${ZOOKEEPER_JAVA_OPTS} \ -Djava.io.tmpdir=/tmp \ -Dzookeeper.jmx.log4j.disable=true \ -Dlog4j.configuration=file:./config/log4j.properties \ org.apache.zookeeper.server.quorum.QuorumPeerMain ./config/zookeeper.properties & # Wait for ZooKeeper to be ok until echo ruok | nc 127.0.0.1 2181 > /dev/null; do sleep 1; done # Configure the Docker HEALTHCHECK export HEALTHCHECK_PORT=9092 export HEALTHCHECK_KIND=tcp echo Starting Kafka exec bin/kafka-run-class.sh kafka.Kafka ./config/server.properties "$@" ================================================ FILE: docker/test-images/zipkin-mysql/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/alpine # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG alpine_version=3.21.3 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-mysql/start-mysql /docker-bin/ COPY docker/test-images/zipkin-mysql/install.sh /install/ COPY zipkin-storage/mysql-v1/src/main/resources/mysql.sql /zipkin-schemas/ FROM ghcr.io/openzipkin/alpine:${alpine_version} as zipkin-mysql LABEL org.opencontainers.image.description="MySQL on Alpine Linux with Zipkin schema pre-installed" # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-mysql"] # Use latest from https://pkgs.alpinelinux.org/packages?name=mysql (without the -r[0-9]) ARG mysql_version=11.8.5-r1 LABEL mysql-version=$mysql_version ENV MYSQL_VERSION=$mysql_version WORKDIR /tmp COPY --from=scratch /zipkin-schemas/* ./install/zipkin-schemas/ COPY --from=scratch /install/install.sh ./install RUN (cd install && ./install.sh) && rm -rf ./install # All content including binaries and logs write under WORKDIR ARG USER=mysql WORKDIR /${USER} EXPOSE 3306 ================================================ FILE: docker/test-images/zipkin-mysql/README.md ================================================ ## zipkin-mysql Docker image The `zipkin-mysql` testing image runs MySQL 10.x initialized with Zipkin's schema for [MySQL storage](../../../zipkin-storage/mysql-v1) integration. To build `openzipkin/zipkin-mysql:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-mysql/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-mysql:test ``` When running with docker-machine, you can connect like so: ```bash $ mysql -h $(docker-machine ip) -u zipkin -pzipkin -D zipkin ``` ================================================ FILE: docker/test-images/zipkin-mysql/install.sh ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # set -eux echo "*** Installing MySQL" apk add --update --no-cache mysql=~${MYSQL_VERSION} mysql-client=~${MYSQL_VERSION} # Fake auth tools install as 10.4.0 install dies otherwise mkdir -p /auth_pam_tool_dir/auth_pam_tool mysql_opts="--user=mysql --basedir=${PWD} --datadir=${PWD}/data --tmpdir=/tmp" # Create the database (stored in the data directory) ln -s /usr/bin bin ln -s /usr/share share mysql_install_db ${mysql_opts} --force # Enable networking echo skip-networking=0 >> /etc/my.cnf echo bind-address=0.0.0.0 >> /etc/my.cnf # Prevent "Bind on unix socket: No such file or directory" mkdir -p /run/mysqld/ && chown mysql /run/mysqld/ echo "*** Starting MySQL" mysqld ${mysql_opts} & temp_mysql_pid=$! # Excessively long timeout to avoid having to create an ENV variable, decide its name, etc. timeout=180 echo "Will wait up to ${timeout} seconds for MySQL to come up before installing Schema" while [ "$timeout" -gt 0 ] && kill -0 ${temp_mysql_pid} && ! mysql --protocol=socket -uroot -e 'SELECT 1' > /dev/null 2>&1; do sleep 1 timeout=$(($timeout - 1)) done echo "*** Installing Schema" mysql --verbose --user=mysql --protocol=socket -uroot <<-'EOF' USE mysql ; DELETE FROM mysql.user ; DROP DATABASE IF EXISTS test ; CREATE DATABASE zipkin ; USE zipkin; SOURCE zipkin-schemas/mysql.sql ; GRANT ALL PRIVILEGES ON zipkin.* TO zipkin@'%' IDENTIFIED BY 'zipkin' WITH GRANT OPTION ; FLUSH PRIVILEGES ; EOF echo "*** Stopping MySQL" kill ${temp_mysql_pid} wait echo "*** Copying database to /mysql/data" mkdir /mysql mv data /mysql/ chown -R mysql /mysql echo "*** Cleaning Up" apk del mysql-client rm bin share # Remove large binaries (cd /usr/bin; rm -f mysql_* aria_* mysqlbinlog myis* test-connect-t mysqlslap innochecksum resolve* my_print_defaults sst_dump) ================================================ FILE: docker/test-images/zipkin-mysql/start-mysql ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts MySQL # # This intentionally locates config using the current working directory, in order to consolidate # Dockerfile instructions to WORKDIR set -eu # Configure the Docker HEALTHCHECK export HEALTHCHECK_PORT=3306 export HEALTHCHECK_KIND=tcp echo Starting MySQL MYSQL_OPTS="--user=mysql --basedir=${PWD} --datadir=${PWD}/data --tmpdir=/tmp" exec mysqld_safe ${MYSQL_OPTS} ================================================ FILE: docker/test-images/zipkin-opensearch2/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # java_version is used for install and runtime layers of zipkin-opensearch2 # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-opensearch2/start-opensearch /docker-bin/ COPY docker/test-images/zipkin-opensearch2/config/ /config/ FROM ghcr.io/openzipkin/java:${java_version} as install WORKDIR /install # Use latest 2.x version from https://opensearch.org/downloads.html # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG opensearch2_version=2.19.0 # Download only the OSS distribution (lacks X-Pack) RUN \ # Connection resets are frequent in GitHub Actions workflows \ wget --random-wait --tries=5 -qO- \ # We don't download bin scripts as we customize for reasons including BusyBox problems https://artifacts.opensearch.org/releases/bundle/opensearch/${opensearch2_version}/opensearch-${opensearch2_version}-linux-x64.tar.gz| tar xz \ --wildcards --strip=1 --exclude=jdk COPY --from=scratch /config/ ./config/ # Use a full Java distribution rather than adding test modules to the # production -jre base layer used by zipkin and zipkin-slim. FROM ghcr.io/openzipkin/java:${java_version} as zipkin-opensearch2 LABEL org.opencontainers.image.description="OpenSearch distribution on OpenJDK and Alpine Linux" ARG opensearch2_version=2.19.0 LABEL opensearch-version=$opensearch2_version # The full license is also included in the image at /opensearch/LICENSE.txt. LABEL org.opencontainers.image.licenses="Apache-2.0" # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["sh", "/usr/local/bin/start-opensearch"] # All content including binaries and logs write under WORKDIR ARG USER=opensearch WORKDIR /${USER} # Ensure the process doesn't run as root RUN adduser -g '' -h ${PWD} -D ${USER} USER ${USER} # Copy binaries and config we installed earlier COPY --from=install --chown=${USER} /install . # Use to set heap, trust store or other system properties. ENV OPENSEARCH_JAVA_OPTS="-Xms512m -Xmx512m -XX:+ExitOnOutOfMemoryError" ENV LIBFFI_TMPDIR=/tmp EXPOSE 9200 ================================================ FILE: docker/test-images/zipkin-opensearch2/README.md ================================================ ## zipkin-opensearch2 Docker image The `zipkin-opensearch2` testing image runs OpenSearch 2.x for [Elasticsearch storage](../../../zipkin-storage/elasticsearch) integration. To build `openzipkin/zipkin-opensearch2:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-opensearch2/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-opensearch2:test ``` You can use the env variable `OPENSEARCH_JAVA_OPTS` to change settings such as heap size for OpenSearch. #### Host setup OpenSearch is [strict](https://github.com/docker-library/docs/tree/master/elasticsearch#host-setup) about virtual memory. You will need to adjust accordingly (especially if you notice OpenSearch crash!) ```bash # If docker is running on your host machine, adjust the kernel setting directly $ sudo sysctl -w vm.max_map_count=262144 # If using docker-machine/Docker Toolbox/Boot2Docker, remotely adjust the same $ docker-machine ssh default "sudo sysctl -w vm.max_map_count=262144" # If using colima, it is similar as well $ colima ssh "sudo sysctl -w vm.max_map_count=262144" ``` ================================================ FILE: docker/test-images/zipkin-opensearch2/config/log4j2.properties ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # status = error appender.console.type = Console appender.console.name = console appender.console.layout.type = PatternLayout appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] [%node_name]%marker %m%n rootLogger.level = info rootLogger.appenderRef.console.ref = console ================================================ FILE: docker/test-images/zipkin-opensearch2/config/opensearch.yml ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # cluster.name: "docker-cluster" network.host: 0.0.0.0 node.name: zipkin-opensearch # Enable development mode and disable bootstrap checks # See https://www.elastic.co/guide/en/elasticsearch/reference/current/bootstrap-checks.html discovery.type: single-node # Avoid deprecation errors: as of 2.x the only accepted value is true. cluster.routing.allocation.disk.watermark.enable_for_single_data_node: true # This is a test image, so we are not afraid to delete all indices. Avoids: # Wildcard expressions or all indices are not allowed action.destructive_requires_name: false # Disable security plugins.security.disabled: true ================================================ FILE: docker/test-images/zipkin-opensearch2/start-opensearch ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # ENTRYPOINT script that starts OpenSearch # # This intentionally locates config using the current working directory, in order to consolidate # Dockerfile instructions to WORKDIR set -eu # Configure the Docker HEALTHCHECK export HEALTHCHECK_PORT=9200 export HEALTHCHECK_PATH=/_cluster/health # Use the 'java.io.tmp' dir and precreate the 'opensearch' folder there export OPENSEARCH_TMPDIR=`java -cp 'classes:lib/*' org.opensearch.tools.launchers.TempDirectory` # The JVM options parser produces the final JVM options to start OpenSearch. # It does this by incorporating JVM options in the following way: # - first, system JVM options are applied (these are hardcoded options in the # parser) # - second, JVM options are read from jvm.options and jvm.options.d/*.options # - third, JVM options from OPENSEARCH_JAVA_OPTS are applied # - fourth, ergonomic JVM options are applied OPENSEARCH_JAVA_OPTS=`java -cp 'classes:lib/*' org.opensearch.tools.launchers.JvmOptionsParser "$PWD/config"` # -cp 'classes:lib/*' allows layers to patch the image without packaging or overwriting jars # We allow security manager (via flag to prevent JRE 21 crash) as OpenSearch.main needs it. exec java -cp 'classes:lib/*' ${OPENSEARCH_JAVA_OPTS} \ -Djava.security.manager=allow \ -Djava.io.tmpdir=/tmp \ -Dlog4j2.disable.jmx=true \ -Dopensearch.path.home=$PWD \ -Dopensearch.path.conf=$PWD/config \ -Dopensearch.distribution.type=docker \ -Dopensearch.bundled_jdk=false \ org.opensearch.bootstrap.OpenSearch "$@" ================================================ FILE: docker/test-images/zipkin-pulsar/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Use latest from https://hub.docker.com/r/apachepulsar/pulsar/tags ARG pulsar_version=4.0.2 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch WORKDIR /docker-bin COPY build-bin/docker/docker-healthcheck /docker-bin/ ARG pulsar_version FROM apachepulsar/pulsar:${pulsar_version} as zipkin-pulsar LABEL pulsar-version=$pulsar_version LABEL org.opencontainers.image.description="Apache Pulsar on Alpine Linux" # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] # Usually, we read env set from pid 1 to get docker-healthcheck parameters. # However, pulsar-server has to start as root even if permissions are dropped # later. So, we expose it in the Dockerfile instead. ENV HEALTHCHECK_PORT=8080 ENV HEALTHCHECK_KIND=http ENV HEALTHCHECK_PATH=/admin/v2/clusters/standalone ENV PULSAR_LOG_ROOT_LEVEL=WARN EXPOSE 8080 6650 CMD ["bin/pulsar", "standalone"] ================================================ FILE: docker/test-images/zipkin-pulsar/README.md ================================================ ## zipkin-pulsar Docker image The `zipkin-pulsar` testing image runs Pulsar for Pulsar collector integration. To build `openzipkin/zipkin-pulsar:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-pulsar/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-pulsar:test ``` You can use the env variable `PULSAR_LOG_ROOT_LEVEL` to change the log level for Pulsar. Defaults to "WARN". ================================================ FILE: docker/test-images/zipkin-rabbitmq/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Use latest from https://hub.docker.com/_/rabbitmq/tags?page=1&name=alpine ARG rabbitmq_version=3.13.7 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch WORKDIR /docker-bin COPY build-bin/docker/docker-healthcheck /docker-bin/ # Bootstrap config including a queue setup out-of-band, exported like so: # rabbitmqctl export_definitions defs.json COPY docker/test-images/zipkin-rabbitmq/config/* /config/ ARG rabbitmq_version FROM rabbitmq:${rabbitmq_version}-alpine as zipkin-rabbitmq LABEL rabbitmq-version=$rabbitmq_version LABEL org.opencontainers.image.description="RabbitMQ on Alpine Linux" # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] COPY --from=scratch /config/* /etc/rabbitmq/ RUN apk add --update --no-cache rabbitmq-c-utils # Usually, we read env set from pid 1 to get docker-healthcheck parameters. # However, rabbitmq-server has to start as root even if permissions are dropped # later. So, we expose it in the Dockerfile instead. ENV HEALTHCHECK_PORT=5672 ENV HEALTHCHECK_KIND=tcp EXPOSE 5672 ================================================ FILE: docker/test-images/zipkin-rabbitmq/README.md ================================================ ## zipkin-rabbitmq Docker image The `zipkin-rabbitmq` testing image runs `rabbitmq-server` for the [RabbitMQ collector](../../../zipkin-collector/rabbitmq) integration. For convenience, this includes the "guest" user and a default queue named "zipkin". To add more queues, exec `amqp-declare-queue` in a running container. To build `openzipkin/zipkin-rabbitmq:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-rabbitmq/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-rabbitmq:test ``` ================================================ FILE: docker/test-images/zipkin-rabbitmq/config/defs.json ================================================ { "permissions": [ { "configure": ".*", "read": ".*", "user": "guest", "vhost": "/", "write": ".*" } ], "bindings": [], "queues": [ { "arguments": {}, "auto_delete": false, "durable": false, "name": "zipkin", "type": "classic", "vhost": "/" } ], "parameters": [], "policies": [], "vhosts": [ { "limits": [], "metadata": { "description": "Default virtual host", "tags": [] }, "name": "/" } ], "exchanges": [], "global_parameters": [], "topic_permissions": [], "users": [ { "hashing_algorithm": "rabbit_password_hashing_sha256", "limits": {}, "name": "guest", "password_hash": "s4XuI8xVMHg3To1FSsFSnsQ2vfgMTP0/XJtmh3plzM/u86Es", "tags": [ "administrator" ] } ] } ================================================ FILE: docker/test-images/zipkin-rabbitmq/config/rabbitmq.conf ================================================ # Does not require management plugin to be enabled. loopback_users.guest = false definitions.import_backend = local_filesystem definitions.local.path = /etc/rabbitmq/defs.json ================================================ FILE: docker/test-images/zipkin-ui/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/alpine # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG alpine_version=3.21.3 # java_version is used during the installation process to build or download the zipkin-lens jar. # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/ /build-bin/ COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-ui/start-nginx /docker-bin/ COPY docker/test-images/zipkin-ui/nginx.conf /conf.d/zipkin.conf.template COPY . /code/ # This version is only used during the install process. Try to be consistent as it reduces layers, # which reduces downloads. FROM ghcr.io/openzipkin/java:${java_version} as install COPY --from=scratch /build-bin/ /build-bin/ WORKDIR /code # Conditions aren't supported in Dockerfile instructions, so we copy source even if it isn't used. COPY --from=scratch /code/ . WORKDIR /install # When true, build-bin/maven/unjar searches /code for the artifact instead of resolving remotely. # /code contains what is allowed in .dockerignore. On problem, ensure .dockerignore is correct. ARG release_from_maven_build=false ENV RELEASE_FROM_MAVEN_BUILD=$release_from_maven_build # Version of the artifact to unjar. Ex. "2.4.5" or "2.4.5-SNAPSHOT" "master" to use the pom version. ARG version=master ENV VERSION=$version ENV MAVEN_PROJECT_BASEDIR=/code RUN if [ "${RELEASE_FROM_MAVEN_BUILD}" == "false" ]; then /build-bin/maybe_install_npm; fi; \ /build-bin/maven/maven_build_or_unjar io.zipkin zipkin-lens ${VERSION} FROM ghcr.io/openzipkin/alpine:$alpine_version as zipkin-ui LABEL org.opencontainers.image.description="NGINX on Alpine Linux hosting the Zipkin UI with Zipkin API proxy_pass" # Use latest from https://pkgs.alpinelinux.org/packages?name=nginx ARG nginx_version=1.28.2-r2 LABEL nginx-version=$nginx_version ENV ZIPKIN_BASE_URL=http://zipkin:9411 # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-nginx"] # Add content and setup NGINX COPY --from=install /install/zipkin-lens/* /var/www/html/zipkin/ COPY --from=scratch /conf.d/ /etc/nginx/conf.d/ RUN apk add --update --no-cache nginx=~${nginx_version} && \ mkdir -p /var/tmp/nginx && chown -R nginx:nginx /var/tmp/nginx # Usually, we read env set from pid 1 to get docker-healthcheck parameters. However, NGINX wipes the # env, so we need to expose it in the Dockerfile instead. ENV HEALTHCHECK_PORT=80 EXPOSE 80 ================================================ FILE: docker/test-images/zipkin-ui/README.md ================================================ ## zipkin-ui Docker image The `zipkin-ui` testing image contains the static parts of the Zipkin UI served directly with NGINX. Besides norms defined in [docker-alpine](https://github.com/openzipkin/docker-alpine), this accepts the following environment variables: * `ZIPKIN_BASE_URL`: The proxied zipkin base URL. Defaults to http://zipkin:9411 To build `openzipkin/zipkin-ui:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-ui/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-ui:test ``` ================================================ FILE: docker/test-images/zipkin-ui/nginx.conf ================================================ user nginx nginx; worker_processes 2; error_log /dev/stdout warn; pid /var/run/nginx.pid; daemon off; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /dev/stdout main; sendfile on; #tcp_nopush on; keepalive_timeout 65; gzip on; gzip_types application/javascript application/json text/css; server_tokens off; types { application/font-woff2 woff2; } server { listen 80; root /var/www/html; index index.html; # Make site accessible from http://set-ip-address.xip.io server_name localhost; charset utf-8; # redirect root as UI is hosted under /zipkin location / { return 302 /zipkin/; } # the entrypoint of the app will expire every day. # this includes links to js assets with random names. location /zipkin/index.html { expires 1d; } location /zipkin { try_files $uri /zipkin/index.html = 404; } # Pass through health check to the proxy for our docker HEALTHCHECK location /health { expires 1s; access_log off; error_log off; # start-nginx overrides the base url to $ZIPKIN_UI_BASEPATH proxy_pass http://localhost:9411; } # accept UI config from the server location /zipkin/config.json { expires 10m; # start-nginx overrides the base url to $ZIPKIN_BASE_URL proxy_pass http://localhost:9411; } # the UI looks for the api under the same relative path location /zipkin/api { expires off; # start-nginx overrides the base url to $ZIPKIN_BASE_URL proxy_pass http://localhost:9411; } # due to minification, the js assets will change names. # this makes them safe to cache longer location ~* \.(?:ico|css|js|gif|jpe?g|png)$ { expires 1y; add_header Cache-Control "public"; } location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { access_log off; log_not_found off; } # Deny .htaccess file access location ~ /\.ht { deny all; } } } ================================================ FILE: docker/test-images/zipkin-ui/start-nginx ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # sed "s~proxy_pass http://localhost:9411~proxy_pass ${ZIPKIN_BASE_URL}~g" \ /etc/nginx/conf.d/zipkin.conf.template > /etc/nginx/nginx.conf echo Starting NGINX exec nginx "$@" ================================================ FILE: docker/test-images/zipkin-uiproxy/Dockerfile ================================================ # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/alpine # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG alpine_version=3.21.3 # java_version is used during the installation process to build or download the zipkin-lens jar. # # Use latest version here: https://github.com/orgs/openzipkin/packages/container/package/java # This is defined in many places because Docker has no "env" script functionality unless you use # docker-compose: When updating, update everywhere. ARG java_version=21.0.7_p6 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. # COPY --from= works around the issue. FROM scratch as scratch COPY build-bin/ /build-bin/ COPY build-bin/docker/docker-healthcheck /docker-bin/ COPY docker/test-images/zipkin-uiproxy/start-nginx /docker-bin/ COPY docker/test-images/zipkin-uiproxy/nginx.conf /conf.d/zipkin.conf.template FROM ghcr.io/openzipkin/alpine:$alpine_version as zipkin-uiproxy LABEL org.opencontainers.image.description="NGINX on Alpine Linux proxying the Zipkin UI with proxy_pass" # Use latest from https://pkgs.alpinelinux.org/packages?name=nginx ARG nginx_version=1.28.2-r2 LABEL nginx-version=$nginx_version ENV ZIPKIN_UI_BASEPATH=/zipkin ENV ZIPKIN_BASE_URL=http://zipkin:9411 # Add HEALTHCHECK and ENTRYPOINT scripts into the default search path COPY --from=scratch /docker-bin/* /usr/local/bin/ # We use start period of 30s to avoid marking the container unhealthy on slow or contended CI hosts HEALTHCHECK --interval=1s --start-period=30s --timeout=5s CMD ["docker-healthcheck"] ENTRYPOINT ["start-nginx"] # Setup NGINX COPY --from=scratch /conf.d/ /etc/nginx/conf.d/ RUN apk add --update --no-cache nginx=~${nginx_version} && \ mkdir -p /var/tmp/nginx && chown -R nginx:nginx /var/tmp/nginx # Usually, we read env set from pid 1 to get docker-healthcheck parameters. However, NGINX wipes the # env, so we need to expose it in the Dockerfile instead. ENV HEALTHCHECK_PORT=80 EXPOSE 80 ================================================ FILE: docker/test-images/zipkin-uiproxy/README.md ================================================ ## zipkin-uiproxy Docker image The `zipkin-uiproxy` testing image proxies the Zipkin UI with NGINX. Besides norms defined in [docker-alpine](https://github.com/openzipkin/docker-alpine), this accepts the following environment variables: * `ZIPKIN_UI_BASEPATH`: The path this proxy serves the UI under. Defaults to /zipkin * `ZIPKIN_BASE_URL`: The proxied zipkin base URL. Defaults to http://zipkin:9411 To build `openzipkin/zipkin-ui:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-uiproxy/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-uiproxy:test ``` ================================================ FILE: docker/test-images/zipkin-uiproxy/nginx.conf ================================================ user nginx nginx; worker_processes 2; error_log /dev/stdout warn; pid /var/run/nginx.pid; daemon off; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /dev/stdout main; sendfile on; keepalive_timeout 65; gzip on; gzip_types application/javascript application/json text/css; server_tokens off; server { listen 80; root /var/www/html; index index.html; # Make site accessible from http://set-ip-address.xip.io server_name localhost; charset utf-8; # start-nginx overrides /zipkin to $ZIPKIN_UI_BASEPATH location /zipkin { # start-nginx overrides the base url to $ZIPKIN_BASE_URL proxy_pass http://localhost:9411/zipkin; proxy_redirect default; } # Pass through health check to the proxy for our docker HEALTHCHECK location /health { expires 1s; access_log off; error_log off; # start-nginx overrides the base url to $ZIPKIN_BASE_URL proxy_pass http://localhost:9411; } location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { access_log off; log_not_found off; } # Deny .htaccess file access location ~ /\.ht { deny all; } } } ================================================ FILE: docker/test-images/zipkin-uiproxy/start-nginx ================================================ #!/bin/sh # # Copyright The OpenZipkin Authors # SPDX-License-Identifier: Apache-2.0 # sed -e "s~proxy_pass http://localhost:9411~proxy_pass ${ZIPKIN_BASE_URL}~g" \ -e "s~location /zipkin~location ${ZIPKIN_UI_BASEPATH}~g" \ /etc/nginx/conf.d/zipkin.conf.template > /etc/nginx/nginx.conf echo Starting NGINX exec nginx "$@" ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.3.2 # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ]; then if [ -f /usr/local/etc/mavenrc ]; then . /usr/local/etc/mavenrc fi if [ -f /etc/mavenrc ]; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ]; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false darwin=false mingw=false case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true ;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then JAVA_HOME="$(/usr/libexec/java_home)" export JAVA_HOME else JAVA_HOME="/Library/Java/Home" export JAVA_HOME fi fi ;; esac if [ -z "$JAVA_HOME" ]; then if [ -r /etc/gentoo-release ]; then JAVA_HOME=$(java-config --jre-home) fi fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw; then [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ && JAVA_HOME="$( cd "$JAVA_HOME" || ( echo "cannot cd into $JAVA_HOME." >&2 exit 1 ) pwd )" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="$(which javac)" if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=$(which readlink) if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin; then javaHome="$(dirname "$javaExecutable")" javaExecutable="$(cd "$javaHome" && pwd -P)/javac" else javaExecutable="$(readlink -f "$javaExecutable")" fi javaHome="$(dirname "$javaExecutable")" javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ]; then if [ -n "$JAVA_HOME" ]; then if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="$( \unset -f command 2>/dev/null \command -v java )" fi fi if [ ! -x "$JAVACMD" ]; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ]; then echo "Warning: JAVA_HOME environment variable is not set." >&2 fi # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ]; then echo "Path not specified to find_maven_basedir" >&2 return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ]; do if [ -d "$wdir"/.mvn ]; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=$( cd "$wdir/.." || exit 1 pwd ) fi # end of workaround done printf '%s' "$( cd "$basedir" || exit 1 pwd )" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then # Remove \r in case we run on Windows within Git Bash # and check out the repository with auto CRLF management # enabled. Otherwise, we may read lines that are delimited with # \r\n and produce $'-Xarg\r' rather than -Xarg due to word # splitting rules. tr -s '\r\n' ' ' <"$1" fi } log() { if [ "$MVNW_VERBOSE" = true ]; then printf '%s\n' "$1" fi } BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1 fi MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} export MAVEN_PROJECTBASEDIR log "$MAVEN_PROJECTBASEDIR" ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" if [ -r "$wrapperJarPath" ]; then log "Found $wrapperJarPath" else log "Couldn't find $wrapperJarPath, downloading it ..." if [ -n "$MVNW_REPOURL" ]; then wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" else wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" fi while IFS="=" read -r key value; do # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) safeValue=$(echo "$value" | tr -d '\r') case "$key" in wrapperUrl) wrapperUrl="$safeValue" break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" log "Downloading from: $wrapperUrl" if $cygwin; then wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget >/dev/null; then log "Found wget ... using wget" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl >/dev/null; then log "Found curl ... using curl" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi else log "Falling back to using Java to download" javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaSource=$(cygpath --path --windows "$javaSource") javaClass=$(cygpath --path --windows "$javaClass") fi if [ -e "$javaSource" ]; then if [ ! -e "$javaClass" ]; then log " - Compiling MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/javac" "$javaSource") fi if [ -e "$javaClass" ]; then log " - Running MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi fi ########################################################################################## # End of extension ########################################################################################## # If specified, validate the SHA-256 sum of the Maven wrapper jar file wrapperSha256Sum="" while IFS="=" read -r key value; do case "$key" in wrapperSha256Sum) wrapperSha256Sum=$value break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" if [ -n "$wrapperSha256Sum" ]; then wrapperSha256Result=false if command -v sha256sum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then wrapperSha256Result=true fi elif command -v shasum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then wrapperSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $wrapperSha256Result = false ]; then echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 exit 1 fi fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] \ && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain # shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. >&2 echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. >&2 echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file SET WRAPPER_SHA_256_SUM="" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B ) IF NOT %WRAPPER_SHA_256_SUM%=="" ( powershell -Command "&{"^ "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ " exit 1;"^ "}"^ "}" if ERRORLEVEL 1 goto error ) @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% ^ %JVM_CONFIG_MAVEN_PROPS% ^ %MAVEN_OPTS% ^ %MAVEN_DEBUG_OPTS% ^ -classpath %WRAPPER_JAR% ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%"=="on" pause if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% cmd /C exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 io.zipkin zipkin-parent 3.6.0-SNAPSHOT pom zipkin zipkin-tests zipkin-junit5 zipkin-storage zipkin-collector zipkin-server ${project.basedir} UTF-8 UTF-8 UTF-8 UTF-8 17 17 17 17 17 17 2.42.0 1.0.0 com.linecorp.armeria 1.37.0 4.2.10.Final 2.21.1 4.19.0 1.16.4 1.3.2 3.5.11 6.2.17 3.5.3 6.2.1 2.0.17 1.11.0 4.9.10 5.13.4 1.13.4 5.21.0 3.27.7 4.3.0 1.21.4 4.12.0 5.6.2 5.1.0 2.13.1 ${project.build.directory}/test/proto ${skipTests} 3.6.0 1.2.8 5.0.0 3.7.1 6.0.0 3.15.0 3.8.1 3.1.2 3.5.0 3.5.1 3.1.3 3.11.2 3.4.2 3.1.1 3.6.0 3.3.1 3.5.3 1.7.0 1.3 Zipkin (Parent) Zipkin (Parent) https://github.com/openzipkin/zipkin 2015 OpenZipkin https://zipkin.io/ The Apache Software License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0.txt repo https://github.com/openzipkin/zipkin scm:git:https://github.com/openzipkin/zipkin.git scm:git:https://github.com/openzipkin/zipkin.git HEAD openzipkin OpenZipkin Gitter https://gitter.im/openzipkin/zipkin ossrh https://oss.sonatype.org/content/repositories/snapshots ossrh https://oss.sonatype.org/service/local/staging/deploy/maven2/ Github https://github.com/openzipkin/zipkin/issues org.junit.jupiter junit-jupiter ${junit-jupiter.version} test org.junit.jupiter junit-jupiter-engine ${junit-jupiter.version} test org.junit.platform junit-platform-launcher ${junit-platform-laucher.version} test org.assertj assertj-core ${assertj.version} test org.mockito mockito-junit-jupiter ${mockito.version} test de.qaware.maven go-offline-maven-plugin ${go-offline-maven-plugin.version} com.mycila license-maven-plugin-git ${license-maven-plugin.version} MAIN com.google.errorprone error_prone_core ${errorprone.version} MAIN org.apache.maven.surefire surefire-junit-platform ${maven-surefire-plugin.version} PLUGIN maven-compiler-plugin ${maven-compiler-plugin.version} true true true maven-deploy-plugin ${maven-deploy-plugin.version} maven-install-plugin ${maven-install-plugin.version} maven-jar-plugin ${maven-jar-plugin.version} false maven-release-plugin ${maven-release-plugin.version} false release true @{project.version} org.sonatype.plugins nexus-staging-maven-plugin ${nexus-staging-maven-plugin.version} org.eclipse.m2e lifecycle-mapping 1.0.0 org.apache.maven.plugins maven-compiler-plugin [3.7,) compile testCompile org.eclipse.m2e.jdt.javaConfigurator maven-eclipse-plugin 2.10 true true maven-shade-plugin ${maven-shade-plugin.version} maven-dependency-plugin ${maven-dependency-plugin.version} unpack-proto generate-sources unpack-dependencies zipkin-proto3 **/*.proto ${unpack-proto.directory} de.m3y.maven wire-maven-plugin ${wire-maven-plugin.version} generate-sources generate-sources ${unpack-proto.directory} zipkin.proto3.* maven-dependency-plugin ${maven-dependency-plugin.version} maven-help-plugin ${maven-help-plugin.version} maven-surefire-plugin ${maven-surefire-plugin.version} false org.junit.jupiter junit-jupiter-engine ${junit-jupiter.version} maven-failsafe-plugin ${maven-surefire-plugin.version} integration-test integration-test verify verify always false false maven-enforcer-plugin ${maven-enforcer-plugin.version} enforce-java enforce [17,18),[21,22) com.mycila license-maven-plugin ${license-maven-plugin.version} ${license.skip}
    ${main.basedir}/src/etc/header.txt
    SLASHSTAR_STYLE SLASHSTAR_STYLE SLASHSTAR_STYLE SLASHSTAR_STYLE SLASHSTAR_STYLE SCRIPT_STYLE XML_STYLE SLASHSTAR_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE SCRIPT_STYLE **/simplelogger.properties **/continuous-build.yml **/*.dockerignore .editorconfig .gitattributes .gitignore .github/** .mvn/** mvnw* etc/header.txt **/nginx.conf **/.idea/** **/node_modules/** **/build/** **/dist/** **/coverage/** **/.babelrc **/.bowerrc **/.editorconfig **/.env.development **/.eslintignore **/.eslintrc **/.eslintrc **/.eslintrc.js **/.linguirc **/testdata/**/*.json **/test/data/**/*.json **/src/translations/** LICENSE **/*.md **/*.bnd **/src/main/resources/zipkin.txt **/src/main/resources/*.yml **/spring.factories **/src/main/resources/*.cql kafka_*/** **/nohup.out src/test/resources/** **/generated/** .dockerignore build-bin/configure_deploy build-bin/configure_test build-bin/deploy build-bin/test true
    com.mycila license-maven-plugin-git ${license-maven-plugin.version} check compile
    include-lens !skipLens zipkin-lens include-benchmarks !skipTests benchmarks error-prone-17+ [17,18),[21,22) maven-compiler-plugin ${maven-compiler-plugin.version} true true true default-compile compile compile true -XDcompilePolicy=simple --should-stop=ifError=FLOW -Xplugin:ErrorProne ${errorprone.args} -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED com.google.errorprone error_prone_core ${errorprone.version} com.google.auto.value auto-value ${auto-value.version} release org.sonatype.plugins nexus-staging-maven-plugin true ossrh https://oss.sonatype.org/ 20 30 true org.apache.maven.plugins maven-gpg-plugin 3.2.3 sign-artifacts verify sign --pinentry-mode loopback maven-source-plugin ${maven-source-plugin.version} attach-sources jar maven-javadoc-plugin ${maven-javadoc-plugin.version} **/internal/*.java **/Internal*.java *.internal.* false none ${maven.compiler.release} attach-javadocs jar package netbeans true project 2 2 2 110 true module-info bnd.bnd org.apache.felix maven-bundle-plugin ${maven-bundle-plugin.version} NONE <_include>-bnd.bnd process-classes manifest maven-jar-plugin default-jar ${project.build.outputDirectory}/META-INF/MANIFEST.MF ${module.name} jar
    ================================================ FILE: src/etc/header.txt ================================================ Copyright The OpenZipkin Authors SPDX-License-Identifier: Apache-2.0 ================================================ FILE: zipkin/RATIONALE.md ================================================ Zipkin Core Library Rationale ============== Much of this rationale is the same as [Brave](https://github.com/openzipkin/brave/blob/master/brave/RATIONALE.md), for consistency reasons. Some aspects, such as Java language level, directly support older versions of Brave. However, looking only at Brave will not give a full impact of choices here. This library is used for streaming pipelines and other instrumentation libraries. It is important to consider carefully before revisiting topics here. While many ideas are our own, there are notable aspects borrowed or adapted from others. It is our goal to cite when we learned something through a prior library, as that allows people to research any rationale that predates our usage. Below is always incomplete, and always improvable. We don't document every thought as it would betray productivity and make this document unreadable. Rationale here should be limited to impactful designs, and aspects non-obvious, non-conventional or subtle. ## Java conventions We only expose types public internally or after significant demand. This keeps the api small and easier to manage when charting migration paths. Otherwise, types are always package private. Methods should only be marked public when they are intentional apis or inheritance requires it. This practice prevents accidental dependence on utilities. ### Why no private symbols? (methods and fields) Zipkin is a library with embedded use cases, such as inside Java agents or Android code. For example, Android has a [hard limit on total methods in an application](https://developer.android.com/build/multidex#avoid). Fields marked private imply accessors in order to share state in the same package. We routinely share state, such as codec internals within a package. If we marked fields private, we'd count against that limit without adding value. Modifiers on fields and methods are distracting to read and increase the size of the bytecode generated during compilation. By recovering the size otherwise spent on private modifiers, we not only avoid hitting limits, but we are also able to add more code with the same jar size. For example, Zipkin 2.21 remains less than 250KiB, with no dependencies, including an in-memory storage implementation and embedded JSON, ProtoBuf and Thrift codecs. This means we do not support sharing our packages with third parties, but we do support an "always share inside a package" in our repository. In other words, we trust our developers to proceed with caution. In the first seven years of project history, we have had no issues raised with this policy. ### Java 8 Up until Zipkin 3, we supported instrumentation of very old applications via source level 1.6. Since then, Brave embedded its own JSON writer and no longer uses this library. Also, other instrumentation libraries, like OpenTelemetry, have a floor version of Java 8. Keeping Java 6 support here limits the LTS we can use to release to max JDK 11. We moved to Java 8 to compromise on these points, knowing Brave 6 can still serve old applications. ### Zero dependency policy Some dependents of this library are instrumentation in nature, and we cannot predict what 3rd party dependencies they will have. Attempting to do that would limit the applicability of this library, which is an anti-goal. Instead, we choose to use nothing except floor Java version features, currently Java 6. Here's an example of when things that seem right aren't. We once dropped our internal `@Nullable` annotation (which is source retention), in favor of JSR 305 (which is runtime retention). In doing so, we got `null` analysis from Intellij. However, we entered a swamp of dependency conflicts, which started with OSGi (making us switch to a service mix bundle for the annotations), and later Java 9 (conflict with apps using jax-ws). In summary, it is easiest for us to have no dependencies also, as we don't inherit tug-of-war between modular frameworks who each have a different "right" answer for even annotations! Incidentally, IntelliJ can be configured to use `zipkin2.internal.Nullable`, now. Search for `Nullable` under inspections to configure this. ### Why `new NullPointerException("xxx == null")` For public entry points, we eagerly check null and throw `NullPointerException` with a message like "xxx == null". This is not a normal pre-condition, such as argument validation, which you'd throw `IllegalArgumentException` for. What's happening here is we are making debugging (literally NPEs are bugs) easier, by not deferring to Java to raise the NPE. If we deferred, it could be confusing which local was null, especially as deferring results in an exception with no message. ================================================ FILE: zipkin/bnd.bnd ================================================ # we block import of shaded packages as maven bundle plugin analyzes the unshaded jar Import-Package: \ !com.google.gson.stream,\ * Export-Package: \ zipkin2,\ zipkin2.codec,\ zipkin2.storage,\ zipkin2.v1,\ zipkin2.internal;zipkin2internal=true;mandatory:=zipkin2internal ================================================ FILE: zipkin/pom.xml ================================================ 4.0.0 io.zipkin zipkin-parent 3.6.0-SNAPSHOT io.zipkin.zipkin2 zipkin Zipkin Core Library zipkin2 -Xep:MutablePublicArray:OFF -Xep:JdkObsolete:OFF ${project.basedir}/.. 8 8 8 com.google.code.gson gson ${gson.version} true com.esotericsoftware kryo ${kryo.version} test maven-shade-plugin package shade false true com.google.code.gson:gson com/google/gson/Strictness.class com/google/gson/stream/JsonReader*.class com/google/gson/stream/JsonToken.class com/google/gson/stream/MalformedJsonException.class com/google/gson/internal/JsonReaderInternalAccess.class com/google/gson/internal/TroubleshootingGuide.class com.google.gson zipkin2.internal.gson ${module.name} false ${main.basedir} META-INF/ LICENSE NOTICE ================================================ FILE: zipkin/src/main/java/zipkin2/Annotation.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import java.io.ObjectStreamException; import java.io.Serializable; import java.io.StreamCorruptedException; /** * Associates an event that explains latency with a timestamp. * *

    Unlike log statements, annotations are often codes: Ex. {@code cache.miss}. */ //@Immutable public final class Annotation implements Comparable, Serializable { // for Spark jobs private static final long serialVersionUID = 0L; public static Annotation create(long timestamp, String value) { if (value == null) throw new NullPointerException("value == null"); return new Annotation(timestamp, value); } /** * Microseconds from epoch. * *

    This value should be set directly by instrumentation, using the most precise value possible. * For example, {@code gettimeofday} or multiplying {@link System#currentTimeMillis} by 1000. */ public long timestamp() { return timestamp; } /** * Usually a short tag indicating an event, like {@code cache.miss} or {@code error} */ public String value() { return value; } /** Compares by {@link #timestamp}, then {@link #value}. */ @Override public int compareTo(Annotation that) { if (this == that) return 0; int byTimestamp = timestamp() < that.timestamp() ? -1 : timestamp() == that.timestamp() ? 0 : 1; if (byTimestamp != 0) return byTimestamp; return value().compareTo(that.value()); } // clutter below mainly due to difficulty working with Kryo which cannot handle AutoValue subclass // See https://github.com/openzipkin/zipkin/issues/1879 final long timestamp; final String value; Annotation(long timestamp, String value) { this.timestamp = timestamp; this.value = value; } @Override public String toString() { return "Annotation{" + "timestamp=" + timestamp + ", " + "value=" + value + "}"; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Annotation)) return false; Annotation that = (Annotation) o; return timestamp == that.timestamp() && value.equals(that.value()); } @Override public int hashCode() { int h = 1; h *= 1000003; h ^= (int) ((timestamp >>> 32) ^ timestamp); h *= 1000003; h ^= value.hashCode(); return h; } // As this is an immutable object (no default constructor), defer to a serialization proxy. Object writeReplace() throws ObjectStreamException { return new SerializedForm(this); } private static final class SerializedForm implements Serializable { static final long serialVersionUID = 0L; final long timestamp; final String value; SerializedForm(Annotation annotation) { timestamp = annotation.timestamp; value = annotation.value; } Object readResolve() throws ObjectStreamException { try { return Annotation.create(timestamp, value); } catch (IllegalArgumentException e) { throw new StreamCorruptedException(e.getMessage()); } } } } ================================================ FILE: zipkin/src/main/java/zipkin2/Call.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; /** * This captures a (usually remote) request and can be used once, either {@link #execute() * synchronously} or {@link #enqueue(Callback) asynchronously}. At any time, from any thread, you * can call {@linkplain #cancel()}, which might stop an in-flight request or prevent one from * occurring. * *

    Implementations should prepare a call such that there's little or no likelihood of late * runtime exceptions. For example, if the call is to get a trace, the call to {@code listSpans} * should propagate input errors vs delay them until a call to {@linkplain #execute()} or * {@linkplain #enqueue(Callback)}. * *

    Ex. *

    {@code
     * // Any translation of an input request to remote parameters should happen here, and any related
     * // errors should propagate here.
     * Call>> listTraces = spanStore.listTraces(request);
     * // When this executes, it should simply run the remote request.
     * List trace = getTraceCall.execute();
     * }
    * *

    An instance of call cannot be invoked more than once, but you can {@linkplain #clone()} an * instance if you need to replay the call. There is no relationship between a call and a number of * remote requests. For example, an implementation that stores spans may make hundreds of remote * requests, possibly retrying on your behalf. * *

    This type owes its design to {@code retrofit2.Call}, which is nearly the same, except limited * to HTTP transports. * * @param the success type, typically not null except when {@code V} is {@linkplain Void}. */ public abstract class Call implements Cloneable { /** * Returns a completed call which has the supplied value. This is useful when input parameters * imply there's no call needed. For example, an empty input might always result in an empty * output. */ public static Call create(V v) { return new Constant<>(v); } @SuppressWarnings("unchecked") public static Call> emptyList() { return Call.create(Collections.emptyList()); } public interface Mapper { V2 map(V1 input); } /** * Maps the result of this call into a different shape, as defined by the {@code mapper} function. * This is used to convert values from one type to another. For example, you could use this to * convert between zipkin v1 and v2 span format. * *

    {@code
       * getTracesV1Call = getTracesV2Call.map(traces -> v2TracesConverter);
       * }
    * *

    This method intends to be used for chaining. That means "this" instance should be discarded * in favor of the result of this method. */ public final Call map(Mapper mapper) { return new Mapping<>(mapper, this); } public interface FlatMapper { Call map(V1 input); } /** * Maps the result of this call into another, as defined by the {@code flatMapper} function. This * is used to chain two remote calls together. For example, you could use this to chain a list IDs * call to a get by IDs call. * *

    {@code
       * getTracesCall = getIdsCall.flatMap(ids -> getTraces(ids));
       *
       * // this would now invoke the chain
       * traces = getTracesCall.enqueue(tracesCallback);
       * }
    * * Cancelation propagates to the mapped call. * *

    This method intends to be used for chaining. That means "this" instance should be discarded * in favor of the result of this method. */ public final Call flatMap(FlatMapper flatMapper) { return new FlatMapping<>(flatMapper, this); } public interface ErrorHandler { /** Attempts to resolve an error. The user must call the callback. */ void onErrorReturn(Throwable error, Callback callback); } /** * Returns a call which can attempt to resolve an exception. This is useful when a remote call * returns an error when a resource is not found. * *

    Here's an example of coercing 404 to empty: *

    {@code
       * call.handleError((error, callback) -> {
       *   if (error instanceof HttpException && ((HttpException) error).code == 404) {
       *     callback.onSuccess(Collections.emptyList());
       *   } else {
       *     callback.onError(error);
       *   }
       * });
       * }
    */ public final Call handleError(ErrorHandler errorHandler) { return new ErrorHandling<>(errorHandler, this); } // Taken from RxJava throwIfFatal, which was taken from scala public static void propagateIfFatal(Throwable t) { if (t instanceof VirtualMachineError) { throw (VirtualMachineError) t; } else if (t instanceof ThreadDeath) { throw (ThreadDeath) t; } else if (t instanceof LinkageError) { throw (LinkageError) t; } } /** * Invokes a request, returning a success value or propagating an error to the caller. Invoking * this more than once will result in an error. To repeat a call, make a copy with {@linkplain * #clone()}. * *

    Eventhough this is a blocking call, implementations may honor calls to {@linkplain * #cancel()} from a different thread. * * @return a success value. Null is unexpected, except when {@code V} is {@linkplain Void}. */ public abstract V execute() throws IOException; /** * Invokes a request asynchronously, signaling the {@code callback} when complete. Invoking this * more than once will result in an error. To repeat a call, make a copy with {@linkplain * #clone()}. */ public abstract void enqueue(Callback callback); /** * Requests to cancel this call, even if some implementations may not support it. For example, a * blocking call is sometimes not cancelable. */ // Boolean isn't returned because some implementations may cancel asynchronously. // Implementing might throw an IOException on execute or callback.onError(IOException) public abstract void cancel(); /** * Returns true if {@linkplain #cancel()} was called. * *

    Calls can fail before being canceled, so true does always mean cancelation caused a call to * fail. That said, successful cancellation does result in a failure. */ // isCanceled exists while isExecuted does not because you do not need the latter to implement // asynchronous bindings, such as rxjava2 public abstract boolean isCanceled(); /** Returns a copy of this object, so you can make an identical follow-up request. */ @Override public abstract Call clone(); static class Constant extends Base { // not final for mock testing final V v; Constant(V v) { this.v = v; } @Override protected V doExecute() { return v; } @Override protected void doEnqueue(Callback callback) { callback.onSuccess(v); } @Override public Call clone() { return new Constant<>(v); } @Override public String toString() { return "ConstantCall{value=" + v + "}"; } @Override public boolean equals(Object o) { if (o == this) return true; if (o instanceof Constant) { Constant that = (Constant) o; return Objects.equals(this.v, that.v); } return false; } @Override public int hashCode() { int h = 1; h *= 1000003; h ^= (v == null) ? 0 : v.hashCode(); return h; } } static final class Mapping extends Base { final Mapper mapper; final Call delegate; Mapping(Mapper mapper, Call delegate) { this.mapper = mapper; this.delegate = delegate; } @Override protected R doExecute() throws IOException { return mapper.map(delegate.execute()); } @Override protected void doEnqueue(final Callback callback) { delegate.enqueue(new Callback() { @Override public void onSuccess(V value) { try { callback.onSuccess(mapper.map(value)); } catch (Throwable t) { callback.onError(t); } } @Override public void onError(Throwable t) { callback.onError(t); } }); } @Override public String toString() { return "Mapping{call=" + delegate + ", mapper=" + mapper + "}"; } @Override public Call clone() { return new Mapping<>(mapper, delegate.clone()); } } static final class FlatMapping extends Base { final FlatMapper flatMapper; final Call delegate; volatile Call mapped; FlatMapping(FlatMapper flatMapper, Call delegate) { this.flatMapper = flatMapper; this.delegate = delegate; } @Override protected R doExecute() throws IOException { return (mapped = flatMapper.map(delegate.execute())).execute(); } @Override protected void doEnqueue(final Callback callback) { delegate.enqueue(new Callback() { @Override public void onSuccess(V value) { try { (mapped = flatMapper.map(value)).enqueue(callback); } catch (Throwable t) { propagateIfFatal(t); callback.onError(t); } } @Override public void onError(Throwable t) { callback.onError(t); } }); } @Override protected void doCancel() { delegate.cancel(); if (mapped != null) mapped.cancel(); } @Override public String toString() { return "FlatMapping{call=" + delegate + ", flatMapper=" + flatMapper + "}"; } @Override public Call clone() { return new FlatMapping<>(flatMapper, delegate.clone()); } } static final class ErrorHandling extends Base { static final Object SENTINEL = new Object(); // to differentiate from null final ErrorHandler errorHandler; final Call delegate; ErrorHandling(ErrorHandler errorHandler, Call delegate) { this.errorHandler = errorHandler; this.delegate = delegate; } @Override protected V doExecute() throws IOException { try { return delegate.execute(); } catch (IOException e) { return handleError(e); } catch (RuntimeException e) { return handleError(e); } catch (Error e) { Call.propagateIfFatal(e); return handleError(e); } } V handleError(T e) throws T { final AtomicReference ref = new AtomicReference(SENTINEL); errorHandler.onErrorReturn(e, new Callback() { @Override public void onSuccess(V value) { ref.set(value); } @Override public void onError(Throwable t) { } }); Object result = ref.get(); if (SENTINEL == result) throw e; return (V) result; } @Override protected void doEnqueue(final Callback callback) { delegate.enqueue(new Callback() { @Override public void onSuccess(V value) { callback.onSuccess(value); } @Override public void onError(Throwable t) { errorHandler.onErrorReturn(t, callback); } }); } @Override protected void doCancel() { delegate.cancel(); } @Override public String toString() { return "ErrorHandling{call=" + delegate + ", errorHandler=" + errorHandler + "}"; } @Override public Call clone() { return new ErrorHandling<>(errorHandler, delegate.clone()); } } public static abstract class Base extends Call { volatile boolean canceled; boolean executed; protected Base() { } @Override public final V execute() throws IOException { synchronized (this) { if (this.executed) throw new IllegalStateException("Already Executed"); this.executed = true; } if (isCanceled()) { throw new IOException("Canceled"); } else { return this.doExecute(); } } protected abstract V doExecute() throws IOException; @Override public final void enqueue(Callback callback) { synchronized (this) { if (this.executed) throw new IllegalStateException("Already Executed"); this.executed = true; } if (isCanceled()) { callback.onError(new IOException("Canceled")); } else { this.doEnqueue(callback); } } protected abstract void doEnqueue(Callback callback); @Override public final void cancel() { this.canceled = true; doCancel(); } protected void doCancel() { } @Override public final boolean isCanceled() { return this.canceled || doIsCanceled(); } protected boolean doIsCanceled() { return false; } } } ================================================ FILE: zipkin/src/main/java/zipkin2/Callback.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import zipkin2.internal.Nullable; /** * A callback of a single result or error. * *

    This is a bridge to async libraries such as CompletableFuture complete, completeExceptionally. * *

    Implementations will call either {@link #onSuccess} or {@link #onError}, but not both. */ public interface Callback { /** * Invoked when computation produces its potentially null value successfully. * *

    When this is called, {@link #onError} won't be. */ void onSuccess(@Nullable V value); /** * Invoked when computation produces a possibly null value successfully. * *

    When this is called, {@link #onSuccess} won't be. */ void onError(Throwable t); } ================================================ FILE: zipkin/src/main/java/zipkin2/CheckResult.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import zipkin2.internal.Nullable; /** * Answers the question: Are operations on this component likely to succeed? * *

    Implementations should initialize the component if necessary. It should test a remote * connection, or consult a trusted source to derive the result. They should use least resources * possible to establish a meaningful result, and be safe to call many times, even concurrently. * * @see CheckResult#OK */ // @Immutable public final class CheckResult { public static final CheckResult OK = new CheckResult(true, null); public static CheckResult failed(Throwable error) { return new CheckResult(false, error); } public boolean ok() { return ok; } /** Present when not ok */ @Nullable public Throwable error() { return error; } final boolean ok; final Throwable error; CheckResult(boolean ok, @Nullable Throwable error) { this.ok = ok; this.error = error; } @Override public String toString() { return "CheckResult{ok=" + ok + ", " + "error=" + error + "}"; } } ================================================ FILE: zipkin/src/main/java/zipkin2/Component.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import java.io.Closeable; import java.io.IOException; /** * Components are object graphs used to compose a zipkin service or client. For example, a storage * component might return a query api. * *

    Components are lazy with regards to I/O. They can be injected directly to other components so * as to avoid crashing the application graph if a network service is unavailable. */ public abstract class Component implements Closeable { /** * Answers the question: Are operations on this component likely to succeed? * *

    Implementations should initialize the component if necessary. It should test a remote * connection, or consult a trusted source to derive the result. They should use least resources * possible to establish a meaningful result, and be safe to call many times, even concurrently. * * @see CheckResult#OK */ public CheckResult check() { return CheckResult.OK; } /** * Closes any network resources created implicitly by the component. * *

    For example, if this created a connection, it would close it. If it was provided one, this * would close any sessions, but leave the connection open. */ @Override public void close() throws IOException { } } ================================================ FILE: zipkin/src/main/java/zipkin2/DependencyLink.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import java.io.ObjectStreamException; import java.io.Serializable; import java.io.StreamCorruptedException; import java.util.Locale; import zipkin2.codec.DependencyLinkBytesDecoder; import zipkin2.codec.DependencyLinkBytesEncoder; import static java.nio.charset.StandardCharsets.UTF_8; /** A dependency link is an edge between two services. */ //@Immutable public final class DependencyLink implements Serializable { // for Spark and Flink jobs private static final long serialVersionUID = 0L; public static Builder newBuilder() { return new Builder(); } /** The parent service name (caller), {@link Span#localServiceName()} if instrumented. */ public String parent() { return parent; } /** The chold service name (callee), {@link Span#localServiceName()} if instrumented. */ public String child() { return child; } /** Total traced calls made from {@link #parent} to {@link #child} */ public long callCount() { return callCount; } /** * {@linkplain #callCount Count of calls} known to be errors (having one {@linkplain Span#tags() * tag} named "error"). */ public long errorCount() { return errorCount; } public Builder toBuilder() { return new Builder(this); } public static final class Builder { String parent, child; long callCount, errorCount; Builder() { } Builder(DependencyLink source) { this.parent = source.parent; this.child = source.child; this.callCount = source.callCount; this.errorCount = source.errorCount; } /** @see #parent() */ public Builder parent(String parent) { if (parent == null) throw new NullPointerException("parent == null"); this.parent = parent.toLowerCase(Locale.ROOT); return this; } /** @see #child() */ public Builder child(String child) { if (child == null) throw new NullPointerException("child == null"); this.child = child.toLowerCase(Locale.ROOT); return this; } /** @see #callCount() */ public Builder callCount(long callCount) { this.callCount = callCount; return this; } /** @see #errorCount() */ public Builder errorCount(long errorCount) { this.errorCount = errorCount; return this; } public DependencyLink build() { String missing = ""; if (parent == null) missing += " parent"; if (child == null) missing += " child"; if (!missing.isEmpty()) throw new IllegalStateException("Missing :" + missing); return new DependencyLink(this); } } @Override public String toString() { return new String(DependencyLinkBytesEncoder.JSON_V1.encode(this), UTF_8); } // clutter below mainly due to difficulty working with Kryo which cannot handle AutoValue subclass // See https://github.com/openzipkin/zipkin/issues/1879 final String parent, child; final long callCount, errorCount; DependencyLink(Builder builder) { parent = builder.parent; child = builder.child; callCount = builder.callCount; errorCount = builder.errorCount; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof DependencyLink)) return false; DependencyLink that = (DependencyLink) o; return parent.equals(that.parent) && child.equals(that.child) && callCount == that.callCount && errorCount == that.errorCount; } @Override public int hashCode() { int h = 1; h *= 1000003; h ^= parent.hashCode(); h *= 1000003; h ^= child.hashCode(); h *= 1000003; h ^= (int) ((callCount >>> 32) ^ callCount); h *= 1000003; h ^= (int) ((errorCount >>> 32) ^ errorCount); return h; } // This is an immutable object, and our encoder is faster than java's: use a serialization proxy. Object writeReplace() throws ObjectStreamException { return new SerializedForm(DependencyLinkBytesEncoder.JSON_V1.encode(this)); } private static final class SerializedForm implements Serializable { private static final long serialVersionUID = 0L; final byte[] bytes; SerializedForm(byte[] bytes) { this.bytes = bytes; } Object readResolve() throws ObjectStreamException { try { return DependencyLinkBytesDecoder.JSON_V1.decodeOne(bytes); } catch (IllegalArgumentException e) { throw new StreamCorruptedException(e.getMessage()); } } } } ================================================ FILE: zipkin/src/main/java/zipkin2/Endpoint.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import java.io.ObjectStreamException; import java.io.Serializable; import java.io.StreamCorruptedException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.Locale; import java.util.Objects; import zipkin2.internal.Nullable; import zipkin2.internal.RecyclableBuffers; import static zipkin2.internal.HexCodec.HEX_DIGITS; /** The network context of a node in the service graph. */ //@Immutable public final class Endpoint implements Serializable { // for Spark and Flink jobs private static final long serialVersionUID = 0L; /** * Lower-case label of this node in the service graph, such as "favstar". Leave absent if * unknown. * *

    This is a primary label for trace lookup and aggregation, so it should be intuitive and * consistent. Many use a name from service discovery. */ @Nullable public String serviceName() { return serviceName; } /** * The text representation of the primary IPv4 address associated with this a connection. Ex. * 192.168.99.100 Absent if unknown. */ @Nullable public String ipv4() { return ipv4; } /** * IPv4 endpoint address packed into 4 bytes or null if unknown. * * @see #ipv6() * @see java.net.Inet4Address#getAddress() */ @Nullable public byte[] ipv4Bytes() { return ipv4Bytes; } /** * The text representation of the primary IPv6 address associated with this a connection. Ex. * 2001:db8::c001 Absent if unknown. * * @see #ipv4() for mapped addresses * @see #ipv6Bytes() */ @Nullable public String ipv6() { return ipv6; } /** * IPv6 endpoint address packed into 16 bytes or null if unknown. * * @see #ipv6() * @see java.net.Inet6Address#getAddress() */ @Nullable public byte[] ipv6Bytes() { return ipv6Bytes; } /** * Port of the IP's socket or null, if not known. * * @see java.net.InetSocketAddress#getPort() */ @Nullable public Integer port() { return port != 0 ? port : null; } /** * Like {@link #port()} except returns a primitive where zero implies absent. * *

    Using this method will avoid allocation, so is encouraged when copying data. */ public int portAsInt() { return port; } public Builder toBuilder() { return new Builder(this); } public static Builder newBuilder() { return new Builder(); } public static final class Builder { String serviceName, ipv4, ipv6; byte[] ipv4Bytes, ipv6Bytes; int port; // zero means null Builder(Endpoint source) { serviceName = source.serviceName; ipv4 = source.ipv4; ipv6 = source.ipv6; ipv4Bytes = source.ipv4Bytes; ipv6Bytes = source.ipv6Bytes; port = source.port; } Builder merge(Endpoint source) { if (serviceName == null) serviceName = source.serviceName; if (ipv4 == null) ipv4 = source.ipv4; if (ipv6 == null) ipv6 = source.ipv6; if (ipv4Bytes == null) ipv4Bytes = source.ipv4Bytes; if (ipv6Bytes == null) ipv6Bytes = source.ipv6Bytes; if (port == 0) port = source.port; return this; } /** Sets {@link Endpoint#serviceName} */ public Builder serviceName(@Nullable String serviceName) { this.serviceName = serviceName == null || serviceName.isEmpty() ? null : serviceName.toLowerCase(Locale.ROOT); return this; } /** Chaining variant of {@link #parseIp(InetAddress)} */ public Builder ip(@Nullable InetAddress addr) { parseIp(addr); return this; } /** * Returns true if {@link Endpoint#ipv4()} or {@link Endpoint#ipv6()} could be parsed from the * input. * *

    Returns boolean not this for conditional parsing. For example: *

    {@code
         * if (!builder.parseIp(input.getHeader("X-Forwarded-For"))) {
         *   builder.parseIp(input.getRemoteAddr());
         * }
         * }
    * * @see #parseIp(String) */ public boolean parseIp(@Nullable InetAddress addr) { if (addr == null) return false; if (addr instanceof Inet4Address) { ipv4 = addr.getHostAddress(); ipv4Bytes = addr.getAddress(); } else if (addr instanceof Inet6Address) { byte[] addressBytes = addr.getAddress(); if (!parseEmbeddedIPv4(addressBytes)) { ipv6 = writeIpV6(addressBytes); ipv6Bytes = addressBytes; } } else { return false; } return true; } /** * Like {@link #parseIp(String)} except this accepts a byte array. * * @param ipBytes byte array whose ownership is exclusively transferred to this endpoint. */ public boolean parseIp(byte[] ipBytes) { if (ipBytes == null) return false; if (ipBytes.length == 4) { ipv4Bytes = ipBytes; ipv4 = writeIpV4(ipBytes); } else if (ipBytes.length == 16) { if (!parseEmbeddedIPv4(ipBytes)) { ipv6 = writeIpV6(ipBytes); ipv6Bytes = ipBytes; } } else { return false; } return true; } static String writeIpV4(byte[] ipBytes) { char[] buf = RecyclableBuffers.shortStringBuffer(); int pos = 0; pos = writeBackwards(ipBytes[0] & 0xff, pos, buf); buf[pos++] = '.'; pos = writeBackwards(ipBytes[1] & 0xff, pos, buf); buf[pos++] = '.'; pos = writeBackwards(ipBytes[2] & 0xff, pos, buf); buf[pos++] = '.'; pos = writeBackwards(ipBytes[3] & 0xff, pos, buf); return new String(buf, 0, pos); } static int writeBackwards(int b, int pos, char[] buf) { if (b < 10) { buf[pos] = HEX_DIGITS[b]; return pos + 1; } int i = pos += b < 100 ? 2 : 3; // We write backwards from right to left. while (b != 0) { int digit = b % 10; buf[--i] = HEX_DIGITS[digit]; b /= 10; } return pos; } /** Chaining variant of {@link #parseIp(String)} */ public Builder ip(@Nullable String ipString) { parseIp(ipString); return this; } /** * Returns true if {@link Endpoint#ipv4()} or {@link Endpoint#ipv6()} could be parsed from the * input. * *

    Returns boolean not this for conditional parsing. For example: *

    {@code
         * if (!builder.parseIp(input.getHeader("X-Forwarded-For"))) {
         *   builder.parseIp(input.getRemoteAddr());
         * }
         * }
    * * @see #parseIp(InetAddress) */ public boolean parseIp(@Nullable String ipString) { if (ipString == null || ipString.isEmpty()) return false; IpFamily format = detectFamily(ipString); if (format == IpFamily.IPv4) { ipv4 = ipString; ipv4Bytes = getIpv4Bytes(ipv4); } else if (format == IpFamily.IPv4Embedded) { ipv4 = ipString.substring(ipString.lastIndexOf(':') + 1); ipv4Bytes = getIpv4Bytes(ipv4); } else if (format == IpFamily.IPv6) { byte[] addressBytes = textToNumericFormatV6(ipString); if (addressBytes == null) return false; ipv6 = writeIpV6(addressBytes); // ensures consistent format ipv6Bytes = addressBytes; } else { return false; } return true; } /** * Use this to set the port to an externally defined value. * * @param port port associated with the endpoint. zero coerces to null (unknown) * @see Endpoint#port() */ public Builder port(@Nullable Integer port) { if (port != null) { if (port > 0xffff) throw new IllegalArgumentException("invalid port " + port); if (port <= 0) port = 0; } this.port = port != null ? port : 0; return this; } /** Sets {@link Endpoint#portAsInt()} */ public Builder port(int port) { if (port > 0xffff) throw new IllegalArgumentException("invalid port " + port); if (port < 0) port = 0; this.port = port; return this; } public Endpoint build() { return new Endpoint(this); } Builder() { } boolean parseEmbeddedIPv4(byte[] ipv6) { for (int i = 0; i < 10; i++) { // Embedded IPv4 addresses start with unset 80 bits if (ipv6[i] != 0) return false; } int flag = (ipv6[10] & 0xff) << 8 | (ipv6[11] & 0xff); if (flag != 0) return false; // IPv4-Compatible or IPv4-Mapped byte o1 = ipv6[12], o2 = ipv6[13], o3 = ipv6[14], o4 = ipv6[15]; if (o1 == 0 && o2 == 0 && o3 == 0 && o4 == 1) { return false; // ::1 is localhost, not an embedded compat address } ipv4 = String.valueOf(o1 & 0xff) + '.' + (o2 & 0xff) + '.' + (o3 & 0xff) + '.' + (o4 & 0xff); ipv4Bytes = new byte[] {o1, o2, o3, o4}; return true; } } enum IpFamily { Unknown, IPv4, IPv4Embedded, IPv6 } /** * Adapted from code in {@code com.google.common.net.InetAddresses.ipStringToBytes}. This version * separates detection from parsing and checks more carefully about embedded addresses. */ static IpFamily detectFamily(String ipString) { boolean hasColon = false; boolean hasDot = false; for (int i = 0, length = ipString.length(); i < length; i++) { char c = ipString.charAt(i); if (c == '.') { hasDot = true; } else if (c == ':') { if (hasDot) return IpFamily.Unknown; // Colons must not appear after dots. hasColon = true; } else if (notHex(c)) { return IpFamily.Unknown; // Everything else must be a decimal or hex digit. } } // Now decide which address family to parse. if (hasColon) { if (hasDot) { int lastColonIndex = ipString.lastIndexOf(':'); if (!isValidIpV4Address(ipString, lastColonIndex + 1, ipString.length())) { return IpFamily.Unknown; } if (lastColonIndex == 1 && ipString.charAt(0) == ':') {// compressed like ::1.2.3.4 return IpFamily.IPv4Embedded; } if (lastColonIndex != 6 || ipString.charAt(0) != ':' || ipString.charAt(1) != ':') { return IpFamily.Unknown; } for (int i = 2; i < 6; i++) { char c = ipString.charAt(i); if (c != 'f' && c != 'F' && c != '0') return IpFamily.Unknown; } return IpFamily.IPv4Embedded; } return IpFamily.IPv6; } else if (hasDot && isValidIpV4Address(ipString, 0, ipString.length())) { return IpFamily.IPv4; } return IpFamily.Unknown; } static boolean notHex(char c) { return (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F'); } static String writeIpV6(byte[] ipv6) { int pos = 0; char[] buf = RecyclableBuffers.shortStringBuffer(); // Compress the longest string of zeros int zeroCompressionIndex = -1; int zeroCompressionLength = -1; int zeroIndex = -1; boolean allZeros = true; for (int i = 0; i < ipv6.length; i += 2) { if (ipv6[i] == 0 && ipv6[i + 1] == 0) { if (zeroIndex < 0) zeroIndex = i; continue; } allZeros = false; if (zeroIndex >= 0) { int zeroLength = i - zeroIndex; if (zeroLength > zeroCompressionLength) { zeroCompressionIndex = zeroIndex; zeroCompressionLength = zeroLength; } zeroIndex = -1; } } // handle all zeros: 0:0:0:0:0:0:0:0 -> :: if (allZeros) return "::"; // handle trailing zeros: 2001:0:0:4:0:0:0:0 -> 2001:0:0:4:: if (zeroCompressionIndex == -1 && zeroIndex != -1) { zeroCompressionIndex = zeroIndex; zeroCompressionLength = 16 - zeroIndex; } int i = 0; while (i < ipv6.length) { if (i == zeroCompressionIndex) { buf[pos++] = ':'; i += zeroCompressionLength; if (i == ipv6.length) buf[pos++] = ':'; continue; } if (i != 0) buf[pos++] = ':'; byte high = ipv6[i++]; byte low = ipv6[i++]; // handle leading zeros: 2001:0:0:4:0000:0:0:8 -> 2001:0:0:4::8 boolean leadingZero; char val = HEX_DIGITS[(high >> 4) & 0xf]; if (!(leadingZero = val == '0')) buf[pos++] = val; val = HEX_DIGITS[high & 0xf]; if (!(leadingZero = (leadingZero && val == '0'))) buf[pos++] = val; val = HEX_DIGITS[(low >> 4) & 0xf]; if (!(leadingZero && val == '0')) buf[pos++] = val; buf[pos++] = HEX_DIGITS[low & 0xf]; } return new String(buf, 0, pos); } // Begin code from com.google.common.net.InetAddresses 23 static final int IPV6_PART_COUNT = 8; @Nullable static byte[] textToNumericFormatV6(String ipString) { // An address can have [2..8] colons, and N colons make N+1 parts. String[] parts = ipString.split(":", IPV6_PART_COUNT + 2); if (parts.length < 3 || parts.length > IPV6_PART_COUNT + 1) { return null; } // Disregarding the endpoints, find "::" with nothing in between. // This indicates that a run of zeroes has been skipped. int skipIndex = -1; for (int i = 1; i < parts.length - 1; i++) { if (parts[i].isEmpty()) { if (skipIndex >= 0) { return null; // Can't have more than one :: } skipIndex = i; } } int partsHi; // Number of parts to copy from above/before the "::" int partsLo; // Number of parts to copy from below/after the "::" if (skipIndex >= 0) { // If we found a "::", then check if it also covers the endpoints. partsHi = skipIndex; partsLo = parts.length - skipIndex - 1; if (parts[0].isEmpty() && --partsHi != 0) { return null; // ^: requires ^:: } if (parts[parts.length - 1].isEmpty() && --partsLo != 0) { return null; // :$ requires ::$ } } else { // Otherwise, allocate the entire address to partsHi. The endpoints // could still be empty, but parseHextet() will check for that. partsHi = parts.length; partsLo = 0; } // If we found a ::, then we must have skipped at least one part. // Otherwise, we must have exactly the right number of parts. int partsSkipped = IPV6_PART_COUNT - (partsHi + partsLo); if (!(skipIndex >= 0 ? partsSkipped >= 1 : partsSkipped == 0)) { return null; } // Now parse the hextets into a byte array. ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT); try { for (int i = 0; i < partsHi; i++) { rawBytes.putShort(parseHextet(parts[i])); } for (int i = 0; i < partsSkipped; i++) { rawBytes.putShort((short) 0); } for (int i = partsLo; i > 0; i--) { rawBytes.putShort(parseHextet(parts[parts.length - i])); } } catch (NumberFormatException ex) { return null; } return rawBytes.array(); } static short parseHextet(String ipPart) { // Note: we already verified that this string contains only hex digits. int hextet = Integer.parseInt(ipPart, 16); if (hextet > 0xffff) { throw new NumberFormatException(); } return (short) hextet; } // End code from com.google.common.net.InetAddresses 23 // Begin code from io.netty.util.NetUtil 4.1 static boolean isValidIpV4Address(String ip, int from, int toExcluded) { int len = toExcluded - from; int i; return len <= 15 && len >= 7 && (i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) && (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && isValidIpV4Word(ip, i + 1, toExcluded); } static boolean isValidIpV4Word(CharSequence word, int from, int toExclusive) { int len = toExclusive - from; char c0, c1, c2; if (len < 1 || len > 3 || (c0 = word.charAt(from)) < '0') { return false; } if (len == 3) { return (c1 = word.charAt(from + 1)) >= '0' && (c2 = word.charAt(from + 2)) >= '0' && ((c0 <= '1' && c1 <= '9' && c2 <= '9') || (c0 == '2' && c1 <= '5' && (c2 <= '5' || (c1 < '5' && c2 <= '9')))); } return c0 <= '9' && (len == 1 || isValidNumericChar(word.charAt(from + 1))); } static boolean isValidNumericChar(char c) { return c >= '0' && c <= '9'; } // End code from io.netty.util.NetUtil 4.1 // clutter below mainly due to difficulty working with Kryo which cannot handle AutoValue subclass // See https://github.com/openzipkin/zipkin/issues/1879 final String serviceName, ipv4, ipv6; final byte[] ipv4Bytes, ipv6Bytes; final int port; Endpoint(Builder builder) { serviceName = builder.serviceName; ipv4 = builder.ipv4; ipv4Bytes = builder.ipv4Bytes; ipv6 = builder.ipv6; ipv6Bytes = builder.ipv6Bytes; port = builder.port; } Endpoint(SerializedForm serializedForm) { serviceName = serializedForm.serviceName; ipv4 = serializedForm.ipv4; ipv4Bytes = serializedForm.ipv4Bytes; ipv6 = serializedForm.ipv6; ipv6Bytes = serializedForm.ipv6Bytes; port = serializedForm.port; } @Override public String toString() { return "Endpoint{" + "serviceName=" + serviceName + ", " + "ipv4=" + ipv4 + ", " + "ipv6=" + ipv6 + ", " + "port=" + port + "}"; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Endpoint)) return false; Endpoint that = (Endpoint) o; return Objects.equals(serviceName, that.serviceName) && Objects.equals(ipv4, that.ipv4) && Objects.equals(ipv6, that.ipv6) && port == that.port; } @Override public int hashCode() { int h = 1; h *= 1000003; h ^= (serviceName == null) ? 0 : serviceName.hashCode(); h *= 1000003; h ^= (ipv4 == null) ? 0 : ipv4.hashCode(); h *= 1000003; h ^= (ipv6 == null) ? 0 : ipv6.hashCode(); h *= 1000003; h ^= port; return h; } // As this is an immutable object (no default constructor), defer to a serialization proxy. Object writeReplace() throws ObjectStreamException { return new SerializedForm(this); } // TODO: replace this with native proto3 encoding static final class SerializedForm implements Serializable { static final long serialVersionUID = 0L; final String serviceName, ipv4, ipv6; final byte[] ipv4Bytes, ipv6Bytes; final int port; SerializedForm(Endpoint endpoint) { serviceName = endpoint.serviceName; ipv4 = endpoint.ipv4; ipv4Bytes = endpoint.ipv4Bytes; ipv6 = endpoint.ipv6; ipv6Bytes = endpoint.ipv6Bytes; port = endpoint.port; } Object readResolve() throws ObjectStreamException { try { return new Endpoint(this); } catch (IllegalArgumentException e) { throw new StreamCorruptedException(e.getMessage()); } } } static byte[] getIpv4Bytes(String ipv4) { byte[] result = new byte[4]; int pos = 0; for (int i = 0, len = ipv4.length(); i < len; ) { char ch = ipv4.charAt(i++); int octet = ch - '0'; if (i == len || (ch = ipv4.charAt(i++)) == '.') { // then we have a single digit octet result[pos++] = (byte) octet; continue; } // push the decimal octet = (octet * 10) + (ch - '0'); if (i == len || (ch = ipv4.charAt(i++)) == '.') { // then we have a two digit octet result[pos++] = (byte) octet; continue; } // otherwise, we have a three digit octet octet = (octet * 10) + (ch - '0'); result[pos++] = (byte) octet; i++; // skip the dot } return result; } } ================================================ FILE: zipkin/src/main/java/zipkin2/Span.java ================================================ /* * Copyright The OpenZipkin Authors * SPDX-License-Identifier: Apache-2.0 */ package zipkin2; import java.io.ObjectStreamException; import java.io.Serializable; import java.io.StreamCorruptedException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.logging.Logger; import zipkin2.codec.SpanBytesDecoder; import zipkin2.codec.SpanBytesEncoder; import zipkin2.internal.Nullable; import zipkin2.internal.RecyclableBuffers; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Level.FINEST; import static zipkin2.internal.HexCodec.HEX_DIGITS; /** * A span is a single-host view of an operation. A trace is a series of spans (often RPC calls) * which nest to form a latency tree. Spans are in the same trace when they share the same trace ID. * The {@link #parentId} field establishes the position of one span in the tree. * *

    The root span is where {@link #parentId} is null and usually has the longest {@link * #duration} in the trace. However, nested asynchronous work can materialize as child spans whose * duration exceed the root span. * *

    Spans usually represent remote activity such as RPC calls, or messaging producers and * consumers. However, they can also represent in-process activity in any position of the trace. For * example, a root span could represent a server receiving an initial client request. A root span * could also represent a scheduled job that has no remote context. * *

    While span identifiers are packed into longs, they should be treated opaquely. ID encoding is * 16 or 32 character lower-hex, to avoid signed interpretation. * *

    Relationship to {@code zipkin.Span}

    * *

    This type is intended to replace use of {@code zipkin.Span}. Particularly, tracers represent * a single-host view of an operation. By making one endpoint implicit for all data, this type does * not need to repeat endpoints on each data like {@code zipkin.Span} does. This results in simpler * and smaller data. */ //@Immutable public final class Span implements Serializable { // for Spark and Flink jobs static final Endpoint EMPTY_ENDPOINT = Endpoint.newBuilder().build(); static final int FLAG_DEBUG = 1 << 1; static final int FLAG_DEBUG_SET = 1 << 2; static final int FLAG_SHARED = 1 << 3; static final int FLAG_SHARED_SET = 1 << 4; private static final long serialVersionUID = 0L; /** * Trace identifier, set on all spans within it. * *

    Encoded as 16 or 32 lowercase hex characters corresponding to 64 or 128 bits. For example, * a 128bit trace ID looks like {@code 4e441824ec2b6a44ffdc9bb9a6453df3}. * *

    Some systems downgrade trace identifiers to 64bit by dropping the left-most 16 characters. * For example, {@code 4e441824ec2b6a44ffdc9bb9a6453df3} becomes {@code ffdc9bb9a6453df3}. */ public String traceId() { return traceId; } /** * The parent's {@link #id} or null if this the root span in a trace. * *

    This is the same encoding as {@link #id}. For example {@code ffdc9bb9a6453df3} */ @Nullable public String parentId() { return parentId; } /** * Unique 64bit identifier for this operation within the trace. * *

    Encoded as 16 lowercase hex characters. For example {@code ffdc9bb9a6453df3} * *

    A span is uniquely identified in storage by ({@linkplain #traceId}, {@linkplain #id()}). */ public String id() { return id; } /** Indicates the primary span type. */ public enum Kind { CLIENT, SERVER, /** * When present, {@link #timestamp()} is the moment a producer sent a message to a destination. * {@link #duration()} represents delay sending the message, such as batching, while {@link * #remoteEndpoint()} indicates the destination, such as a broker. * *

    Unlike {@link #CLIENT}, messaging spans never share a span ID. For example, the {@link * #CONSUMER} of the same message has {@link #parentId()} set to this span's {@link #id()}. */ PRODUCER, /** * When present, {@link #timestamp()} is the moment a consumer received a message from an * origin. {@link #duration()} represents delay consuming the message, such as from backlog, * while {@link #remoteEndpoint()} indicates the origin, such as a broker. * *

    Unlike {@link #SERVER}, messaging spans never share a span ID. For example, the {@link * #PRODUCER} of this message is the {@link #parentId()} of this span. */ CONSUMER } /** When present, used to interpret {@link #remoteEndpoint} */ @Nullable public Kind kind() { return kind; } /** * Span name in lowercase, rpc method for example. * *

    Conventionally, when the span name isn't known, name = "unknown". */ @Nullable public String name() { return name; } /** * Epoch microseconds of the start of this span, possibly absent if this an incomplete span. * *

    This value should be set directly by instrumentation, using the most precise value * possible. For example, {@code gettimeofday} or multiplying {@link System#currentTimeMillis} by * 1000. * *

    There are three known edge-cases where this could be reported absent: * *

      *
    • A span was allocated but never started (ex not yet received a timestamp)
    • *
    • The span's start event was lost
    • *
    • Data about a completed span (ex tags) were sent after the fact
    • *