From 63b921394747112781635b426f1632fed10aad7b Mon Sep 17 00:00:00 2001 From: Tomasz Rojek Date: Tue, 30 Mar 2021 06:45:42 +0200 Subject: [PATCH] Add instrumentation for httpd - Apache HTTP Server (#7) --- instrumentation/httpd/.bazelversion | 1 + instrumentation/httpd/.clang-format | 58 +++ .../httpd/.github/workflows/ci.yml | 64 +++ instrumentation/httpd/.gitignore | 45 ++ instrumentation/httpd/BUILD | 11 + instrumentation/httpd/Dockerfile | 38 ++ instrumentation/httpd/LICENSE | 201 +++++++++ instrumentation/httpd/Makefile | 38 ++ instrumentation/httpd/README.md | 69 ++++ instrumentation/httpd/WORKSPACE | 30 ++ instrumentation/httpd/build.sh | 3 + instrumentation/httpd/create-otel-load.sh | 9 + instrumentation/httpd/httpd_install_otel.sh | 17 + instrumentation/httpd/opentelemetry.conf | 35 ++ instrumentation/httpd/setup-buildtools.sh | 26 ++ instrumentation/httpd/setup_environment.sh | 7 + instrumentation/httpd/src/otel/BUILD | 21 + instrumentation/httpd/src/otel/mod_otel.cpp | 387 ++++++++++++++++++ .../httpd/src/otel/opentelemetry.cpp | 142 +++++++ .../httpd/src/otel/opentelemetry.h | 117 ++++++ .../httpd/tests/00-apache-configtest.sh | 17 + .../httpd/tests/01-create-root-span.sh | 49 +++ .../httpd/tests/02-accept-inbound-trace.sh | 38 ++ .../httpd/tests/03-accept-inbound-b3.sh | 37 ++ .../httpd/tests/04-accept-inbound-b3-multi.sh | 37 ++ .../httpd/tests/05-check-batch-spans.sh | 41 ++ .../httpd/tests/06-check-batch-spans-x1000.sh | 28 ++ .../httpd/tests/07-create-outbound-spans.sh | 86 ++++ .../tests/08-create-outbound-spans-b3.sh | 78 ++++ instrumentation/httpd/tests/run-all.sh | 9 + instrumentation/httpd/tests/setup-tools.sh | 5 + instrumentation/httpd/tests/tools.sh | 189 +++++++++ .../httpd/tools/check-formatting.sh | 42 ++ instrumentation/httpd/tools/format-bazel.sh | 35 ++ instrumentation/httpd/tools/format-code.sh | 46 +++ instrumentation/httpd/tools/setup-tools.sh | 47 +++ 36 files changed, 2103 insertions(+) create mode 100644 instrumentation/httpd/.bazelversion create mode 100644 instrumentation/httpd/.clang-format create mode 100644 instrumentation/httpd/.github/workflows/ci.yml create mode 100644 instrumentation/httpd/.gitignore create mode 100644 instrumentation/httpd/BUILD create mode 100644 instrumentation/httpd/Dockerfile create mode 100644 instrumentation/httpd/LICENSE create mode 100644 instrumentation/httpd/Makefile create mode 100644 instrumentation/httpd/README.md create mode 100644 instrumentation/httpd/WORKSPACE create mode 100755 instrumentation/httpd/build.sh create mode 100755 instrumentation/httpd/create-otel-load.sh create mode 100755 instrumentation/httpd/httpd_install_otel.sh create mode 100644 instrumentation/httpd/opentelemetry.conf create mode 100755 instrumentation/httpd/setup-buildtools.sh create mode 100755 instrumentation/httpd/setup_environment.sh create mode 100644 instrumentation/httpd/src/otel/BUILD create mode 100644 instrumentation/httpd/src/otel/mod_otel.cpp create mode 100644 instrumentation/httpd/src/otel/opentelemetry.cpp create mode 100644 instrumentation/httpd/src/otel/opentelemetry.h create mode 100755 instrumentation/httpd/tests/00-apache-configtest.sh create mode 100755 instrumentation/httpd/tests/01-create-root-span.sh create mode 100755 instrumentation/httpd/tests/02-accept-inbound-trace.sh create mode 100755 instrumentation/httpd/tests/03-accept-inbound-b3.sh create mode 100755 instrumentation/httpd/tests/04-accept-inbound-b3-multi.sh create mode 100755 instrumentation/httpd/tests/05-check-batch-spans.sh create mode 100755 instrumentation/httpd/tests/06-check-batch-spans-x1000.sh create mode 100755 instrumentation/httpd/tests/07-create-outbound-spans.sh create mode 100755 instrumentation/httpd/tests/08-create-outbound-spans-b3.sh create mode 100755 instrumentation/httpd/tests/run-all.sh create mode 100755 instrumentation/httpd/tests/setup-tools.sh create mode 100755 instrumentation/httpd/tests/tools.sh create mode 100755 instrumentation/httpd/tools/check-formatting.sh create mode 100755 instrumentation/httpd/tools/format-bazel.sh create mode 100755 instrumentation/httpd/tools/format-code.sh create mode 100755 instrumentation/httpd/tools/setup-tools.sh diff --git a/instrumentation/httpd/.bazelversion b/instrumentation/httpd/.bazelversion new file mode 100644 index 000000000..0b2eb36f5 --- /dev/null +++ b/instrumentation/httpd/.bazelversion @@ -0,0 +1 @@ +3.7.2 diff --git a/instrumentation/httpd/.clang-format b/instrumentation/httpd/.clang-format new file mode 100644 index 000000000..ecc7ffe73 --- /dev/null +++ b/instrumentation/httpd/.clang-format @@ -0,0 +1,58 @@ +# See Clang docs: http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium + +# Allow double brackets such as std::vector>. +Standard: Cpp11 + +# Indent 2 spaces at a time. +IndentWidth: 2 + +# Keep lines under 100 columns long. +ColumnLimit: 100 + +# Always break before braces +BreakBeforeBraces: Custom +BraceWrapping: +# TODO(lujc) wait for clang-format-9 support in Chromium tools +# AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + + # Keeps extern "C" blocks unindented. + AfterExternBlock: false + +# Indent case labels. +IndentCaseLabels: true + +# Right-align pointers and references +PointerAlignment: Right + +# ANGLE likes to align things as much as possible. +AlignOperands: true +AlignConsecutiveAssignments: true + +# Use 2 space negative offset for access modifiers +AccessModifierOffset: -2 + +# TODO(jmadill): Decide if we want this on. Doesn't have an "all or none" mode. +AllowShortCaseLabelsOnASingleLine: false + +# Useful for spacing out functions in classes +KeepEmptyLinesAtTheStartOfBlocks: true + +# Indent nested PP directives. +IndentPPDirectives: AfterHash + +# Include blocks style +IncludeBlocks: Preserve \ No newline at end of file diff --git a/instrumentation/httpd/.github/workflows/ci.yml b/instrumentation/httpd/.github/workflows/ci.yml new file mode 100644 index 000000000..03f25e1dc --- /dev/null +++ b/instrumentation/httpd/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + name: Build module + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup buildtools + run: | + sudo ./setup-buildtools.sh + sudo ./setup_environment.sh + - name: Compile + run: ./build.sh + + clang_format: + name: Check code formatting + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup formatting tool (clang) + run: | + sudo ./tools/setup-tools.sh code + - name: Check formatting + run: ./tools/format-code.sh + + build_format: + name: Check build formatting + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup formatting tool (buildifier) + run: | + sudo ./tools/setup-tools.sh buildifier + - name: Check formatting + run: ./tools/format-bazel.sh + + e2e_tests: + name: Run end-to-end tests + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup buildtools + run: | + sudo ./tests/setup-tools.sh + sudo ./setup-buildtools.sh + sudo ./setup_environment.sh + - name: Compile + run: ./build.sh + - name: Run tests + run: | + ./create-otel-load.sh + sudo ./httpd_install_otel.sh + cd tests && sudo ./run-all.sh diff --git a/instrumentation/httpd/.gitignore b/instrumentation/httpd/.gitignore new file mode 100644 index 000000000..b9a32e893 --- /dev/null +++ b/instrumentation/httpd/.gitignore @@ -0,0 +1,45 @@ +# Ref. https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Bazel files +/bazel-* + +# Mac +.DS_Store + +# Output directories +/out +/out.* +# Indicator that the tools were deployed +.buildtools \ No newline at end of file diff --git a/instrumentation/httpd/BUILD b/instrumentation/httpd/BUILD new file mode 100644 index 000000000..1535506c4 --- /dev/null +++ b/instrumentation/httpd/BUILD @@ -0,0 +1,11 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary") + +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "otel.so", + linkshared = 1, + deps = [ + "//src/otel:otelmodlib", + ], +) diff --git a/instrumentation/httpd/Dockerfile b/instrumentation/httpd/Dockerfile new file mode 100644 index 000000000..f461d1c68 --- /dev/null +++ b/instrumentation/httpd/Dockerfile @@ -0,0 +1,38 @@ +FROM ubuntu:18.04 + +######################################### +# copy setup stuff from opentelemetry-cpp +######################################### + +WORKDIR /setup-ci + +ADD setup-buildtools.sh /setup-ci/setup-buildtools.sh + +RUN /setup-ci/setup-buildtools.sh + +######################### +# now build plugin itself +######################### + +ADD setup_environment.sh /setup/setup_environment.sh + +RUN /setup/setup_environment.sh + +COPY src /root/src +COPY .clang-format /root +COPY tools /root/tools + +COPY .bazelversion /root +COPY BUILD /root +COPY WORKSPACE /root + +WORKDIR /root +COPY create-otel-load.sh /root +COPY build.sh /root +COPY opentelemetry.conf /root +COPY httpd_install_otel.sh /root +RUN /root/build.sh + +# TODO: check apache configuration (apachectl configtest) + +# TODO: run tests? diff --git a/instrumentation/httpd/LICENSE b/instrumentation/httpd/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/instrumentation/httpd/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/instrumentation/httpd/Makefile b/instrumentation/httpd/Makefile new file mode 100644 index 000000000..d5407a615 --- /dev/null +++ b/instrumentation/httpd/Makefile @@ -0,0 +1,38 @@ + + +BUILD_IMAGE=opentelemetry-httpd-mod-dev +CONTAINER_NAME=otel-httpd + +.PHONY: help build clean devsh rmcnt rming start stop + +all: help + +help: + @echo Possible targets are: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +build: ## build docker image with development tools + docker build -t $(BUILD_IMAGE) . + +start: build ## start new docker container + docker run -v "$(PWD)/src":/mnt/host --network host --privileged --name "$(CONTAINER_NAME)" -it $(BUILD_IMAGE) /bin/bash -l + +startifnotrunning: + + +# stop: ## stop docker image + +rmcnt: ## remove docker container + docker container rm $(CONTAINER_NAME) || true + +rmimg: ## remove docker image + docker image rm $(BUILD_IMAGE) + +clean: rmcnt rmimg ## clean both docker container and image + +devsh: ## attach to existing container with shell (developer shell) + if [ "docker container inspect (CONTAINER_NAME)" ]; then \ + docker exec -it $(CONTAINER_NAME) bash; \ + else \ + $(MAKE) start; \ + fi; diff --git a/instrumentation/httpd/README.md b/instrumentation/httpd/README.md new file mode 100644 index 000000000..59640b15e --- /dev/null +++ b/instrumentation/httpd/README.md @@ -0,0 +1,69 @@ +# httpd (Apache) OpenTelemetry module + +## Features + +- Supports exporters: plain file, OTLP +- Supports batch processor +- Supports propagators: [w3c trace-context](https://www.w3.org/TR/trace-context/), [B3](https://github.com/openzipkin/b3-propagation) + +## Requirements + +- C++11 +- [OpenTelemetry-Cpp](https://github.com/open-telemetry/opentelemetry-cpp) +- httpd (Apache) ver. 2.4.x on Linux (Current release tested only with Ubuntu LTS 18.04) +- Bazel 3.7.x + +## Build + development + +Build can be done within docker or alternatively check Development section for Ubuntu below. Execute: `make build` to start build process. + +After build is successful enable module for httpd (Apache) the `httpd_install_otel.sh` (prepared for Docker Image) script can be used for this. + +### Development + +For development purposes (to get inside docker) execute `make start` and `make devsh` for each extra terminal. + +Build is done with Bazel. Execute: `./build.sh` and it should create file `otel.so` inside `bazel-out/k8-opt/bin` directory. + +You can just link `/mnt/host` into `/root/src`: + +```bash +cd /root +mv src src-org +ln -s /mnt/host src +``` + +When local changes are made, you need to restart the `httpd` server to load new version of library, to do that: `apachectl stop; ./build.sh && apachectl start` + +### Development (Ubuntu) + +On Ubuntu you need packages listed here: [setup_environment.sh](./setup_environment.sh) which are prerequisites to compile opentelemetry-cpp and here: [setup-buildtools.sh](./setup-buildtools.sh) for apache development stuff. Then just execute [bulid.sh](./build.sh). + +### Run formatting check + +Please make sure that code is well formatted with this command: + +```bash +./tools/check-formatting.sh +``` + +### Testing + +Integration tests exists in `tests` directory. Please run `run-all.sh` to check functionality. + +## Configuration + +At the moment only one global exporter is allowed for entire daemon. Include it following way: + +``` + +OpenTelemetryExporter file +OpenTelemetryPath /tmp/spans + +``` + +in a master configuration which usually is in `/etc/apache2` directory. + +If no `OpenTelemetryPath` is specified then spans goes to standard error output which is apache error log. + +More detailed information about configuration options can be found in [provided configuration file](./opentelemetry.conf) diff --git a/instrumentation/httpd/WORKSPACE b/instrumentation/httpd/WORKSPACE new file mode 100644 index 000000000..873ba99a8 --- /dev/null +++ b/instrumentation/httpd/WORKSPACE @@ -0,0 +1,30 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# Load OpentTelemetry-CPP dependency +http_archive( + name = "io_opentelemetry_cpp", + sha256 = "2621cb0efd9bae78a1f06866cf8e96417446a6a70568ed6f804a6cb91d916db1", + strip_prefix = "opentelemetry-cpp-bd68a22ff5f2343de68f9d56a68bc53ecd69d567", + urls = [ + "https://github.com/open-telemetry/opentelemetry-cpp/archive/bd68a22ff5f2343de68f9d56a68bc53ecd69d567.tar.gz" + ], +) + +# Load OpenTelemetry dependencies after load. +load("@io_opentelemetry_cpp//bazel:repository.bzl", "opentelemetry_cpp_deps") + +opentelemetry_cpp_deps() + +# Load gRPC dependencies after load. +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") + +grpc_deps() + +# Load extra gRPC dependencies due to https://github.com/grpc/grpc/issues/20511 +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") + +grpc_extra_deps() + +load("@upb//bazel:repository_defs.bzl", "bazel_version_repository") + +bazel_version_repository(name = "upb_bazel_version") diff --git a/instrumentation/httpd/build.sh b/instrumentation/httpd/build.sh new file mode 100755 index 000000000..ca4df3e4b --- /dev/null +++ b/instrumentation/httpd/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +bazel build -c opt :all diff --git a/instrumentation/httpd/create-otel-load.sh b/instrumentation/httpd/create-otel-load.sh new file mode 100755 index 000000000..ed449ce66 --- /dev/null +++ b/instrumentation/httpd/create-otel-load.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# create configuration file for httpd (Apache) +cat << EOF > opentelemetry.load +# C++ Standard library +LoadFile /usr/lib/x86_64-linux-gnu/libstdc++.so.6 + +LoadModule otel_module $PWD/bazel-out/k8-opt/bin/otel.so +EOF diff --git a/instrumentation/httpd/httpd_install_otel.sh b/instrumentation/httpd/httpd_install_otel.sh new file mode 100755 index 000000000..85ea88a29 --- /dev/null +++ b/instrumentation/httpd/httpd_install_otel.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# when in docker image is FROM ubuntu:18.04 +APACHE_ALL_MODULES_DIR=/etc/apache2/mods-available +# TODO check: sudo a2enmod opentel + +ln -fs ${SCRIPT_DIR}/opentelemetry.load ${APACHE_ALL_MODULES_DIR} +ln -fs ${SCRIPT_DIR}/opentelemetry.conf ${APACHE_ALL_MODULES_DIR} + +a2enmod opentelemetry + +exit $? + +# TODO: fixme when in docker image FROM httpd +# HTTPD_DIR=/usr/local/apache2/modules/ diff --git a/instrumentation/httpd/opentelemetry.conf b/instrumentation/httpd/opentelemetry.conf new file mode 100644 index 000000000..c16111465 --- /dev/null +++ b/instrumentation/httpd/opentelemetry.conf @@ -0,0 +1,35 @@ + +# OpenTelemetryExporter set exporter type: +# file - means put spans into file +# otlp - means use otlp + +OpenTelemetryExporter file +# if you don't specify path for exporter by default standard error will be used +# which is just simply apache error log +OpenTelemetryPath /tmp/output-spans + +# OpenTelemetryExporter otlp +# OpenTelemetryEndpoint host.docker.internal:55680 + +# OpenTelemetryBatch for batch configuration. Takes 3 arguments: +# Max Queue Size +# Delay (in milliseconds, 1000 = 1s) +# Max Export Batch Size + +# OpenTelemetryBatch 10 5000 5 + +# OpenTelemetryPropagators sets which context propagator should be used (defaults to none) +# currently supported values are (only one can be specified at the moment): +# trace-context - headers: tracestate, traceparent +# b3 - single header (b3) +# b3-multiheader - headers: X-B3-TraceId, X-B3-SpanId + +# OpenTelemetryPropagators b3-multiheader + +# OpenTelemetryIgnoreInbound (defaults to true) indicates that we don't trust incoming context. +# This is safe when httpd is an edge server with traffic from Internet. Set it to false only +# if you run httpd in safe environment. + +# OpenTelemetryIgnoreInbound off + + diff --git a/instrumentation/httpd/setup-buildtools.sh b/instrumentation/httpd/setup-buildtools.sh new file mode 100755 index 000000000..904120266 --- /dev/null +++ b/instrumentation/httpd/setup-buildtools.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +apt-get install -qq automake +apt-get install -qq libtool-bin +apt-get install -qq curl +apt-get install -qq libcurl4-openssl-dev +apt-get install -qq zlib1g-dev +apt-get install -qq git +apt-get install -qq build-essential +apt-get install -qq libssl-dev +apt-get install -qq libsqlite3-dev +# Stock sqlite may be too old +#apt install libsqlite3-dev +apt-get install -qq wget + +# bazelisk is not in apt repository +BAZELISK_VERSION=v1.7.4 + +wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/$BAZELISK_VERSION/bazelisk-linux-amd64 +chmod +x /usr/local/bin/bazel + + +## Change owner from root to current dir owner +chown -R `stat . -c %u:%g` * diff --git a/instrumentation/httpd/setup_environment.sh b/instrumentation/httpd/setup_environment.sh new file mode 100755 index 000000000..8b0b50c94 --- /dev/null +++ b/instrumentation/httpd/setup_environment.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +apt-get install --no-install-recommends --no-install-suggests -y \ + apache2 \ + apache2-dev diff --git a/instrumentation/httpd/src/otel/BUILD b/instrumentation/httpd/src/otel/BUILD new file mode 100644 index 000000000..8251b73d9 --- /dev/null +++ b/instrumentation/httpd/src/otel/BUILD @@ -0,0 +1,21 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "otelmodlib", + srcs = glob(["*.cpp"]), + hdrs = glob(["*.h"]), + copts = [ + "-I/usr/include/apache2", + "-I/usr/include/apr-1.0", + "-fpermissive", + ], + deps = [ + "@io_opentelemetry_cpp//api", + "@io_opentelemetry_cpp//exporters/ostream:ostream_span_exporter", + "@io_opentelemetry_cpp//exporters/otlp:otlp_exporter", + "@io_opentelemetry_cpp//sdk/src/trace", + ], + alwayslink = 1, +) diff --git a/instrumentation/httpd/src/otel/mod_otel.cpp b/instrumentation/httpd/src/otel/mod_otel.cpp new file mode 100644 index 000000000..99c880721 --- /dev/null +++ b/instrumentation/httpd/src/otel/mod_otel.cpp @@ -0,0 +1,387 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "opentelemetry.h" + +#include "opentelemetry/context/context.h" +#include "opentelemetry/trace/propagation/b3_propagator.h" +#include "opentelemetry/trace/propagation/http_trace_context.h" + +#include "ap_config.h" +#include "httpd.h" +// httpd.h has to be before those two and after opentelemetry because it #define's OK 0 +#include "http_config.h" +#include "http_protocol.h" +#include "mod_proxy.h" + +namespace +{ + +const std::string kSpanNamePrefix = "HTTP "; + +using namespace httpd_otel; + +const char kOpenTelemetryKeyNote[] = "OTEL"; +const char kOpenTelemetryKeyOutboundNote[] = "OTEL_PROXY"; + +static nostd::string_view HttpdGetter(const apr_table_t &hdrs, nostd::string_view trace_type) +{ + auto fnd = apr_table_get(&hdrs, std::string(trace_type).c_str()); + return fnd ? fnd : ""; +} + +static void HttpdSetter(apr_table_t &hdrs, + nostd::string_view trace_type, + nostd::string_view trace_description) +{ + apr_table_set(&hdrs, std::string(trace_type).c_str(), + std::string(trace_description).c_str()); +} + +// propagators +opentelemetry::trace::propagation::HttpTraceContext PropagatorTraceContext; +opentelemetry::trace::propagation::B3Propagator PropagatorB3SingleHeader; +opentelemetry::trace::propagation::B3PropagatorMultiHeader PropagatorB3MultiHeader; + +// from: +// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions +HttpdStartSpanAttributes GetAttrsFromRequest(request_rec *r) +{ + HttpdStartSpanAttributes res; + res.server_name = r->server->server_hostname; + if (r->method) + { + res.method = r->method; + } + res.scheme = ap_run_http_scheme(r); + if (r->hostname) + { + res.host = r->hostname; + } + if (r->unparsed_uri) + { + res.target = r->unparsed_uri; + } + switch (r->proto_num) + { // TODO: consider using ap_get_protocol for other flavors + case 1000: + res.flavor = "1.0"; + break; + case 1001: + res.flavor = "1.1"; + break; + } + res.client_ip = r->useragent_ip; + res.net_ip = r->connection->client_ip; + return res; +} + +// main function +// TODO: fix this for scenarios where apache configuration is just updated (apachectl -k graceful) +static void opentel_child_created(apr_pool_t *p, server_rec *s) +{ + initTracer(); +} + +// Starting span as early as possible (quick handler): +// http://www.fmc-modeling.org/category/projects/apache/amp/3_3Extending_Apache.html#fig:_Apache:_request-processing_+_Module_callbacks_PN +static int opentel_handler(request_rec *r, int /* lookup_uri */ ) +{ + // track main request + // TODO: find a scenario where it makes a difference (perhaps something with mod_rewrite?) + request_rec *req = r->main ? r->main : r; + + ExtraRequestData *req_data; + apr_pool_userdata_get((void **)&req_data, kOpenTelemetryKeyNote, req->pool); + + if (req_data) + { // we already have span started + return DECLINED; + } + + // start new span + // re-use exisiting memory pool provided by apache + auto ExtraRequestDataBuffer = apr_palloc(req->pool, sizeof(ExtraRequestData)); + // and use placement new + req_data = new (ExtraRequestDataBuffer) ExtraRequestData; + + apr_pool_userdata_setn(ExtraRequestDataBuffer, kOpenTelemetryKeyNote, + (apr_status_t(*)(void *))ExtraRequestData::Destruct, req->pool); + + if (!config.ignore_inbound && config.propagation != OtelPropagation::NONE) + { + opentelemetry::v0::context::Context ctx_new, + ctx_cur = opentelemetry::context::RuntimeContext::GetCurrent(); + switch (config.propagation) + { + default: + ctx_new = PropagatorTraceContext.Extract(HttpdGetter, *req->headers_in, ctx_cur); + break; + case OtelPropagation::B3_SINGLE_HEADER: + case OtelPropagation::B3_MULTI_HEADER: + ctx_new = PropagatorB3SingleHeader.Extract(HttpdGetter, *req->headers_in, ctx_cur); + } + req_data->token = opentelemetry::context::RuntimeContext::Attach(ctx_new); + } + + opentelemetry::trace::StartSpanOptions startOpts; + startOpts.kind = opentelemetry::trace::SpanKind::kServer; + auto span = get_tracer()->StartSpan(kSpanNamePrefix + req->method, startOpts); + + req_data->span = span; + req_data->StartSpan(GetAttrsFromRequest(req)); + + return DECLINED; +} + +static int opentel_log_transaction(request_rec *r) +{ + request_rec *req = r->main ? r->main : r; + + ExtraRequestData *req_data; + apr_pool_userdata_get((void **)&req_data, kOpenTelemetryKeyNote, req->pool); + + if (!req_data) + { + return DECLINED; + } + // finish span + req_data->EndSpan({ + req->status, + req->bytes_sent + }); + + return DECLINED; +} + +///////////////////////////////////////////////// +// Outbound span created when mod_proxy is used +///////////////////////////////////////////////// + +static int proxy_fixup_handler(request_rec *r) +{ // adding outbound headers and setting span attribiutes + request_rec *req = r->main ? r->main : r; + + ExtraRequestData *req_data; + apr_pool_userdata_get((void **)&req_data, kOpenTelemetryKeyNote, req->pool); + + if (!req_data) + { // main (parent) span not started? + return DECLINED; + } + + ExtraRequestData *req_data_out; + apr_pool_userdata_get((void **)&req_data_out, kOpenTelemetryKeyOutboundNote, req->pool); + + if (req_data_out) + { // we already have span started + return DECLINED; + } + + // start new span + // re-use exisiting memory pool provided by apache + auto ExtraRequestDataBuffer = apr_palloc(r->pool, sizeof(ExtraRequestData)); + // and use placement new + req_data_out = new (ExtraRequestDataBuffer) ExtraRequestData; + + apr_pool_userdata_setn(ExtraRequestDataBuffer, kOpenTelemetryKeyOutboundNote, + (apr_status_t(*)(void *))ExtraRequestData::Destruct, req->pool); + + opentelemetry::trace::StartSpanOptions startOpts; + startOpts.kind = opentelemetry::trace::SpanKind::kClient; + startOpts.parent = req_data->span->GetContext(); + auto span = get_tracer()->StartSpan(kSpanNamePrefix + req->method, startOpts); + + req_data_out->span = span; + HttpdStartSpanAttributes startAttrs; + startAttrs.server_name = r->server->server_hostname; + startAttrs.method = req->method; + startAttrs.url = req->filename; + if (startAttrs.url.substr(0,6) == "proxy:") + { + startAttrs.url = startAttrs.url.substr(6); + } + + req_data_out->StartSpan(startAttrs); + + auto scope = get_tracer()->WithActiveSpan(span); + + // mod_proxy simply copies request headers from client therefore inject is into headers_in + // instead of headers_out + switch (config.propagation) + { + case OtelPropagation::TRACE_CONTEXT: + PropagatorTraceContext.Inject(HttpdSetter, *req->headers_in, opentelemetry::context::RuntimeContext::GetCurrent()); + break; + case OtelPropagation::B3_SINGLE_HEADER: + PropagatorB3SingleHeader.Inject(HttpdSetter, *req->headers_in, opentelemetry::context::RuntimeContext::GetCurrent()); + break; + case OtelPropagation::B3_MULTI_HEADER: + PropagatorB3MultiHeader.Inject(HttpdSetter, *req->headers_in, opentelemetry::context::RuntimeContext::GetCurrent()); + break; + default: // suppress warning + break; + } + + return DECLINED; +} + +static int proxy_end_handler(int *status, request_rec *r) +{ + request_rec *req = r->main ? r->main : r; + + ExtraRequestData *req_data_out; + apr_pool_userdata_get((void **)&req_data_out, kOpenTelemetryKeyOutboundNote, req->pool); + + if (!req_data_out) + { + return DECLINED; + } + + int st_code = (status && *status) ? *status:r->status; + char *proxy_error = apr_table_get(r->notes, "error-notes"); + if (proxy_error) + { + req_data_out->span->SetStatus(opentelemetry::trace::StatusCode::kError, proxy_error); + } + + // finish span + req_data_out->EndSpan({ + st_code, + req->bytes_sent + }); + req_data_out->Destruct(req_data_out); + + return DECLINED; +} + +///////////////////////////////////////////////// +// PARSING CONFIGURATION OPTIONS +///////////////////////////////////////////////// + +static const char *otel_set_exporter(cmd_parms *cmd, void *cfg, const char *arg) +{ + if (!strcasecmp(arg, "file")) + config.type = OtelExporterType::OSTREAM; + else if (!strcasecmp(arg, "otlp")) + config.type = OtelExporterType::OTLP; + else + return "Unknown exporter type - can be file or otlp"; + + return NULL; +} + +const char *otel_set_propagator(cmd_parms *cmd, void *cfg, const char *arg) +{ + if (!strcasecmp(arg, "trace-context")) + config.propagation = OtelPropagation::TRACE_CONTEXT; + else if (!strcasecmp(arg, "b3")) + config.propagation = OtelPropagation::B3_SINGLE_HEADER; + else if (!strcasecmp(arg, "b3-multiheader")) + config.propagation = OtelPropagation::B3_MULTI_HEADER; + else + return "Unknown propagator type - can be trace-context or b3 or b3-multiheader"; + + return NULL; +} + +const char *otel_set_ignoreInbound(cmd_parms *cmd, void *cfg, int flag) +{ + config.ignore_inbound = flag; + return NULL; +} + +const char *otel_set_path(cmd_parms *cmd, void *cfg, const char *arg) +{ + config.fname = arg; + return NULL; +} + +const char *otel_set_endpoint(cmd_parms *cmd, void *cfg, const char *arg) +{ + config.endpoint = arg; + return NULL; +} + +const char *otel_cfg_batch(cmd_parms *cmd, + void *cfg, + const char *max_queue_size, + const char *schedule_delay_millis, + const char *max_export_batch_size) +{ + config.batch_opts.max_queue_size = atoi(max_queue_size); + config.batch_opts.schedule_delay_millis = std::chrono::milliseconds(atoi(schedule_delay_millis)); + config.batch_opts.max_export_batch_size = atoi(max_export_batch_size); + return NULL; +} + +} // namespace + +extern "C" { + +static void opentel_register_hooks(apr_pool_t *p) +{ + ap_hook_child_init(opentel_child_created, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_quick_handler(opentel_handler, NULL, NULL, APR_HOOK_FIRST); + ap_hook_log_transaction(opentel_log_transaction, NULL, NULL, APR_HOOK_LAST); + // for outbound transactions (proxy hooks) + // I've tried scheme_handler, canon_handler but they weren't working + // pre_request is only for balancer:// schema + APR_OPTIONAL_HOOK(proxy, fixups, proxy_fixup_handler, NULL, NULL, APR_HOOK_REALLY_FIRST); + APR_OPTIONAL_HOOK(proxy, request_status, proxy_end_handler, NULL, NULL, + APR_HOOK_MIDDLE); +} + +static const command_rec opentel_directives[] = { + AP_INIT_TAKE1("OpenTelemetryExporter", + otel_set_exporter, + NULL, + RSRC_CONF, + "Set specific exporter type"), + AP_INIT_TAKE1("OpenTelemetryPath", otel_set_path, NULL, RSRC_CONF, "Set path for exporter"), + AP_INIT_TAKE1("OpenTelemetryEndpoint", + otel_set_endpoint, + NULL, + RSRC_CONF, + "Set endpoint for exporter"), + AP_INIT_TAKE3("OpenTelemetryBatch", + otel_cfg_batch, + NULL, + RSRC_CONF, + "Configure batch processing"), + AP_INIT_FLAG("OpenTelemetryIgnoreInbound", + otel_set_ignoreInbound, + NULL, + RSRC_CONF, + "Enable or disable context propagation from incoming requests."), + AP_INIT_TAKE1("OpenTelemetryPropagators", + otel_set_propagator, + NULL, + RSRC_CONF, + "Configure propagators"), + {NULL}}; + +/* the main config structure */ +AP_DECLARE_MODULE(otel) = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + NULL, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + opentel_directives, /* table of config file commands */ + opentel_register_hooks /* register hooks */ +}; +} // extern "C" diff --git a/instrumentation/httpd/src/otel/opentelemetry.cpp b/instrumentation/httpd/src/otel/opentelemetry.cpp new file mode 100644 index 000000000..9ca57cfff --- /dev/null +++ b/instrumentation/httpd/src/otel/opentelemetry.cpp @@ -0,0 +1,142 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "opentelemetry.h" + +#include +#include + +#include "opentelemetry/exporters/ostream/span_exporter.h" +#include "opentelemetry/exporters/otlp/otlp_exporter.h" +#include "opentelemetry/sdk/trace/batch_span_processor.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +namespace httpd_otel +{ + +// TODO: use semantic conventions https://github.com/open-telemetry/opentelemetry-cpp/issues/566 +const nostd::string_view kAttrHTTPServerName = "http.server_name"; +const nostd::string_view kAttrHTTPMethod = "http.method"; +const nostd::string_view kAttrHTTPScheme = "http.scheme"; +const nostd::string_view kAttrHTTPHost = "http.host"; +const nostd::string_view kAttrHTTPTarget = "http.target"; +const nostd::string_view kAttrHTTPUrl = "http.url"; +const nostd::string_view kAttrHTTPFlavor = "http.flavor"; +const nostd::string_view kAttrHTTPClientIP = "http.client_ip"; +const nostd::string_view kAttrNETPeerIP = "net.peer.ip"; +const nostd::string_view kAttrHTTPStatusCode = "http.status_code"; +const nostd::string_view kAttrHTTPResponseContentLen = "http.response_content_length"; + +OtelConfig config; + +void initTracer() +{ + if (!config.fname.empty()) + { + config.output_file.open(config.fname.c_str(), std::ios::app); + if (!config.output_file) + { + return; // Error opening OTel output file + } + } + + std::unique_ptr exporter; + switch (config.type) + { + default: // suppress warning + break; + case OtelExporterType::OSTREAM: + exporter = std::unique_ptr( + new opentelemetry::exporter::trace::OStreamSpanExporter( + config.fname.empty() ? std::cerr : config.output_file)); + break; + case OtelExporterType::OTLP: + opentelemetry::exporter::otlp::OtlpExporterOptions opts; + opts.endpoint = config.endpoint; + exporter = std::unique_ptr( + new opentelemetry::exporter::otlp::OtlpExporter(opts)); + break; + } + + std::shared_ptr processor; + + if (config.batch_opts.max_queue_size) + { + processor = std::make_shared( + std::move(exporter), config.batch_opts); + } + else + { + processor = std::make_shared(std::move(exporter)); + } + + auto provider = nostd::shared_ptr( + new sdktrace::TracerProvider(processor)); + + // Set the global trace provider + opentelemetry::trace::Provider::SetTracerProvider(provider); +} + +nostd::shared_ptr get_tracer() +{ + auto provider = opentelemetry::trace::Provider::GetTracerProvider(); + return provider->GetTracer(KHTTPDOTelTracerName); +} + +void ExtraRequestData::StartSpan(const HttpdStartSpanAttributes& attrs) +{ + startAttrs = attrs; + span->SetAttribute(kAttrHTTPServerName, startAttrs.server_name); + if (!startAttrs.method.empty()) + { + span->SetAttribute(kAttrHTTPMethod, startAttrs.method); + } + if (!startAttrs.scheme.empty()) + { + span->SetAttribute(kAttrHTTPScheme, startAttrs.scheme); + } + if (!startAttrs.host.empty()) + { + span->SetAttribute(kAttrHTTPHost, startAttrs.host); + } + if (!startAttrs.target.empty()) + { + span->SetAttribute(kAttrHTTPTarget, startAttrs.target); + } + if (!startAttrs.url.empty()) + { + span->SetAttribute(kAttrHTTPUrl, startAttrs.url); + } + if (!startAttrs.flavor.empty()) + { + span->SetAttribute(kAttrHTTPFlavor, startAttrs.flavor); + } + span->SetAttribute(kAttrHTTPClientIP, startAttrs.client_ip); + if (attrs.net_ip != startAttrs.client_ip) + { + span->SetAttribute(kAttrNETPeerIP, startAttrs.net_ip); + } +} + +void ExtraRequestData::EndSpan(const HttpdEndSpanAttributes& attrs) +{ + span->SetAttribute(kAttrHTTPStatusCode, attrs.status); + span->SetAttribute(kAttrHTTPResponseContentLen, attrs.bytes_sent); +} + +} // namespace httpd_otel diff --git a/instrumentation/httpd/src/otel/opentelemetry.h b/instrumentation/httpd/src/otel/opentelemetry.h new file mode 100644 index 000000000..349461146 --- /dev/null +++ b/instrumentation/httpd/src/otel/opentelemetry.h @@ -0,0 +1,117 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTPD_OPENTELEMETRY_H_ +#define HTTPD_OPENTELEMETRY_H_ + +#include + +#include "opentelemetry/exporters/ostream/span_exporter.h" +#include "opentelemetry/sdk/trace/batch_span_processor.h" +#include "opentelemetry/trace/provider.h" + +namespace httpd_otel +{ + +const nostd::string_view KHTTPDOTelTracerName = "httpd"; + +enum class OtelExporterType +{ + NONE = 0, + OSTREAM = 1, + OTLP = 2 +}; + +enum class OtelPropagation +{ + NONE = 0, + TRACE_CONTEXT = 1, + B3_SINGLE_HEADER = 2, + B3_MULTI_HEADER = 3 +}; + +struct OtelConfig +{ + OtelExporterType type; + std::string fname; // for exporter type ostream + std::string endpoint; // for exporter type otlp + std::ofstream output_file; + // configuration for batch processing + opentelemetry::sdk::trace::BatchSpanProcessorOptions batch_opts; + // context propagation + bool ignore_inbound; + OtelPropagation propagation; + OtelConfig() : ignore_inbound(true) {} +}; + +extern OtelConfig config; + +struct HttpdStartSpanAttributes +{ + std::string server_name; + std::string method; + std::string scheme; + std::string host; + std::string target; + std::string url; // as an alternative for scheme + host + target + std::string flavor; + std::string client_ip; + std::string net_ip; +}; + +struct HttpdEndSpanAttributes +{ + int status; + int64_t bytes_sent; +}; + +// this object is created using placement new therefore all destructors needs +// to be called explictly inside Destruct method +struct ExtraRequestData +{ // context which we pass + nostd::unique_ptr token; + nostd::shared_ptr span; + HttpdStartSpanAttributes startAttrs; + HttpdEndSpanAttributes endAttrs; + // Sets attribiutes for HTTP request. + void StartSpan(const HttpdStartSpanAttributes &attrs); + void EndSpan(const HttpdEndSpanAttributes &attrs); + // we are called from apache when apr_pool_t is being cleaned after request + // due to fact that callback is "C" style we are receiving This as 1st argument + static int Destruct(ExtraRequestData *This) + { + if (This->token) + { + This->token.release(); + This->token = nullptr; + } + if (This->span) + { + This->span->End(); + This->span = nullptr; + } + return 0; // return value (apr_status_t) is ignored by run_cleanups + } +}; + +// Initializes OpenTelemetry module. +void initTracer(); +// Returns default (global) tracer. +nostd::shared_ptr get_tracer(); + +} // namespace httpd_otel + +#endif // HTTPD_OPENTELEMETRY_H_ diff --git a/instrumentation/httpd/tests/00-apache-configtest.sh b/instrumentation/httpd/tests/00-apache-configtest.sh new file mode 100755 index 000000000..e1afeaf14 --- /dev/null +++ b/instrumentation/httpd/tests/00-apache-configtest.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +TEST_NAME="Check that OpenTelemetry module can be loaded by httpd (Apache)" + +. tools.sh + +setup_test () { + +cat << EOF > ${HTTPD_CONFIG} + +Error "OpenTelemetry module is required to run all tests. Run a2enmod otel" + +EOF + +} + +run $@ diff --git a/instrumentation/httpd/tests/01-create-root-span.sh b/instrumentation/httpd/tests/01-create-root-span.sh new file mode 100755 index 000000000..7af2f1096 --- /dev/null +++ b/instrumentation/httpd/tests/01-create-root-span.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +TEST_NAME="Check that span is created" + +. tools.sh + +setup_test () { + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +EOF + +} + +run_test() { + ${CURL_CMD} ${ENDPOINT_URL} || fail "Unable to download main page" +} + + +check_results() { + echo Checking that exactly one span was created + count '{' 1 # total one span + + echo Checking span fields + check name 'HTTP GET' + check 'span kind' Server + [ "`getSpanField span_id`" != "`getSpanField parent_span_id`" ] || fail "Bad span: span.id same as parent span.id" + + echo Checking span attribiutes + declare -A SPAN_ATTRS + # transforms "http.method: GET, http.flavor: http, ..." into SPAN_ATTRS[http.method] = GET + IFS=',' read -ra my_array <<< "`getSpanField attributes`" + for i in "${my_array[@]}"; do + TMP_KEY=${i%%:*} + TMP_KEY=${TMP_KEY# } + TMP_VAL=${i##*:} + TMP_VAL=${TMP_VAL:1} + SPAN_ATTRS[${TMP_KEY}]="${TMP_VAL}" + done + + [[ "${SPAN_ATTRS[http.method]}" == "GET" ]] || fail "Bad span attribiute http.method ${SPAN_ATTRS[http.method]}" + [[ "${SPAN_ATTRS[http.target]}" == "/" ]] || fail "Bad span attribiute http.target ${SPAN_ATTRS[http.target]}" + [[ "${SPAN_ATTRS[http.status_code]}" == "200" ]] || fail "Bad span attribiute http.status_code ${SPAN_ATTRS[http.status_code]}" + [[ "${SPAN_ATTRS[http.flavor]}" == "1.1" ]] || fail "Bad span attribiute http.flavor ${SPAN_ATTRS[http.flavor]}" + [[ "${SPAN_ATTRS[http.scheme]}" == "http" ]] || fail "Bad span attribiute http.scheme ${SPAN_ATTRS[http.scheme]}" +} + +run $@ diff --git a/instrumentation/httpd/tests/02-accept-inbound-trace.sh b/instrumentation/httpd/tests/02-accept-inbound-trace.sh new file mode 100755 index 000000000..a3c22b219 --- /dev/null +++ b/instrumentation/httpd/tests/02-accept-inbound-trace.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +TEST_NAME="Check that incoming span is accepted (same trace_id and parent_span_id are set)" + +. tools.sh + +setup_test () { + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +OpenTelemetryPropagators trace-context +OpenTelemetryIgnoreInbound Off +EOF + +} + +TRACE_VER="00" +TRACE_ID="00112233445566778899aabbccddeeff" +TRACE_PARENT="0101010101010101" +TRACE_FLAGS="01" + +HEADER="traceparent: ${TRACE_VER}-${TRACE_ID}-${TRACE_PARENT}-${TRACE_FLAGS}" + +run_test() { + ${CURL_CMD} -H "${HEADER}" ${ENDPOINT_URL} || fail "Unable to download main page" +} + +check_results() { + echo Checking that exactly one span was created + count '{' 1 # total one span + + echo "Checking that trace_id and span_id was properly parsed" + check 'trace_id' ${TRACE_ID} + check 'parent_span_id' ${TRACE_PARENT} +} + +run $@ diff --git a/instrumentation/httpd/tests/03-accept-inbound-b3.sh b/instrumentation/httpd/tests/03-accept-inbound-b3.sh new file mode 100755 index 000000000..6ef9261c1 --- /dev/null +++ b/instrumentation/httpd/tests/03-accept-inbound-b3.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +TEST_NAME="Check that incoming span is accepted with b3 format (same trace_id and parent_span_id are set)" + +. tools.sh + +setup_test () { + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +OpenTelemetryPropagators b3 +OpenTelemetryIgnoreInbound Off +EOF + +} + +TRACE_ID="0abcdefabcdefabcdefabcdefabcdef0" +TRACE_PARENT="0123456789abcdef" +TRACE_FLAGS="1" + +HEADER="b3: ${TRACE_ID}-${TRACE_PARENT}-${TRACE_FLAGS}" + +run_test() { + ${CURL_CMD} -H "${HEADER}" ${ENDPOINT_URL} || fail "Unable to download main page" +} + +check_results() { + echo Checking that exactly one span was created + count '{' 1 # total one span + + echo "Checking that trace_id and span_id was properly parsed" + check 'trace_id' ${TRACE_ID} + check 'parent_span_id' ${TRACE_PARENT} +} + +run $@ diff --git a/instrumentation/httpd/tests/04-accept-inbound-b3-multi.sh b/instrumentation/httpd/tests/04-accept-inbound-b3-multi.sh new file mode 100755 index 000000000..035296a5e --- /dev/null +++ b/instrumentation/httpd/tests/04-accept-inbound-b3-multi.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +TEST_NAME="Check that incoming span is accepted with b3 multiline header (same trace_id and parent_span_id are set)" + +. tools.sh + +setup_test () { + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +OpenTelemetryPropagators b3-multiheader +OpenTelemetryIgnoreInbound Off +EOF + +} + +TRACE_ID="00abba0000abba0000abba0000abba00" +TRACE_PARENT="aabbaabbaabbaabb" + +HEADER_1="X-B3-TraceId: ${TRACE_ID}" +HEADER_2="X-B3-SpanId: ${TRACE_PARENT}" + +run_test() { + ${CURL_CMD} -H "${HEADER_1}" -H "${HEADER_2}" ${ENDPOINT_URL} || fail "Unable to download main page" +} + +check_results() { + echo Checking that exactly one span was created + count '{' 1 # total one span + + echo "Checking that trace_id and span_id was properly parsed" + check 'trace_id' ${TRACE_ID} + check 'parent_span_id' ${TRACE_PARENT} +} + +run $@ diff --git a/instrumentation/httpd/tests/05-check-batch-spans.sh b/instrumentation/httpd/tests/05-check-batch-spans.sh new file mode 100755 index 000000000..979d85e9e --- /dev/null +++ b/instrumentation/httpd/tests/05-check-batch-spans.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +TEST_NAME="Check that 5 requests creates 5 spans (with batch)" + +. tools.sh + +fail () { + printf '%s\n' "$1" >&2 ## Send message to stderr. Exclude >&2 if you don't want it that way. + exit "${2-1}" ## Return a code specified by $2 or 1 by default. +} + +setup_test () { + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +OpenTelemetryBatch 10 5000 5 +EOF + +} + +run_test() { + ${CURL_CMD} ${ENDPOINT_URL} || fail "Unable to download main page" + ${CURL_CMD} ${ENDPOINT_URL} || fail "Unable to download main page" + ${CURL_CMD} ${ENDPOINT_URL} || fail "Unable to download main page" + ${CURL_CMD} ${ENDPOINT_URL} || fail "Unable to download main page" + ${CURL_CMD} ${ENDPOINT_URL} || fail "Unable to download main page" +} + + + +check_results() { + echo Checking that all ${TOTAL_SPANS} were created + count '{' 5 # span count is good +} + +teardown_test() { + rm -rf ${OUTPUT_SPANS} +} + +run $@ diff --git a/instrumentation/httpd/tests/06-check-batch-spans-x1000.sh b/instrumentation/httpd/tests/06-check-batch-spans-x1000.sh new file mode 100755 index 000000000..1da6113a0 --- /dev/null +++ b/instrumentation/httpd/tests/06-check-batch-spans-x1000.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +TEST_NAME="Check that 1000 requests creates 1000 spans with batch processing" + +. tools.sh + +TOTAL_SPANS=1000 + +setup_test () { + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +OpenTelemetryBatch 10 5000 5 +EOF + +} + +run_test() { + httperf --timeout=5 --client=0/1 --server=${ENDPOINT_ADDR} --port=${ENDPOINT_PORT} --uri=/ --rate=150 --send-buffer=4096 --recv-buffer=16384 --num-conns=${TOTAL_SPANS} --num-calls=1 +} + +check_results() { + echo Checking that all ${TOTAL_SPANS} were created + count '{' ${TOTAL_SPANS} # span count is good +} + +run $@ diff --git a/instrumentation/httpd/tests/07-create-outbound-spans.sh b/instrumentation/httpd/tests/07-create-outbound-spans.sh new file mode 100755 index 000000000..848fcbf99 --- /dev/null +++ b/instrumentation/httpd/tests/07-create-outbound-spans.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +TEST_NAME="Check that outbound span is created when forwarding request with mod_proxy" + +. tools.sh + +setup_test () { + +EXTRA_HTTPD_MODS="proxy proxy_http" + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +OpenTelemetryPropagators trace-context +OpenTelemetryIgnoreInbound Off + + + ProxyPass "/bar" "${ENDPOINT_PROXY}/hello-world" + ProxyPassReverse "/bar" "${ENDPOINT_PROXY}/hello-world" + +EOF + +} + +TRACE_VER="00" +TRACE_ID="ffeeddccbbaa99887766554433221100" +TRACE_PARENT="0102030405060708" +TRACE_FLAGS="01" + +TRACE_STATE="foo=bar" + +HEADER_1="traceparent: ${TRACE_VER}-${TRACE_ID}-${TRACE_PARENT}-${TRACE_FLAGS}" +HEADER_2="tracestate: ${TRACE_STATE}" + +run_test() { + ${CURL_CMD} -H "${HEADER_1}" ${ENDPOINT_URL} || fail "Unable to download main page" +} + +# this is what will be returned via proxy (this is called from netcat) +proxy () { + STATUS="200 OK" + + echo "---${REQ_METHOD}--- ${REQ_URL} "`date '+%H:%M:%S (%s)'` > /dev/tty + env | grep -i hdr > /dev/tty + echo '---------' > /dev/tty + + # extract everything before dash + RECV_STATE=${HDR_TRACESTATE%%-*} + + if [ "${HDR_TRACESTATE}" != "${TRACE_STATE}" ]; then + echo "Wrong header for tracer test" > /dev/tty + STATUS="401 Test failed" + fi + + echo -e "HTTP/1.1 ${STATUS}\r\nConnection: close\r\n\r\n" + + date + sleep 1 + echo "traceparent Header was: ${HDR_TRACEPARENT}" + echo "tracestate Header was: ${HDR_TRACESTATE}" + date +} + +run_test() { + curl --fail -v -H "${HEADER_1}" -H "${HEADER_2}" ${ENDPOINT_URL}/bar || failHttpd "Unable to download page with proxy enabled" +} + + +check_results() { + echo "Checking that exactly two spans were created (client one and server one)" + count '{' 2 # total two spans = one incoming and one outgoing + + echo "Checking that trace_id was properly parsed" + check 'trace_id' ${TRACE_ID} + + # there should be at least one span type client + grep "span kind : Client" ${OUTPUT_SPANS} || fail "Span kind client not found" + # there should be at least one span created (server one) with set parent_span_id as what we've sent + grep "parent_span_id: ${TRACE_PARENT}" ${OUTPUT_SPANS} || fail "Inbound propagation failed - not found parent span_id ${TRACE_PARENT}" + # now exclude it and find remaning one + PROXY_SPAN_ID=`grep -v "parent_span_id: ${TRACE_PARENT}" ${OUTPUT_SPANS} | grep "parent_span_id:" | cut -d ':' -f 2 || fail "Inbound propagation failed - not found parent span_id"` + # remaining parent_span_id should be span_id of the server one + grep "span_id :${PROXY_SPAN_ID}" ${OUTPUT_SPANS} || fail "Outbound propagation failed - it has not set proper parent span" +} + +run $@ diff --git a/instrumentation/httpd/tests/08-create-outbound-spans-b3.sh b/instrumentation/httpd/tests/08-create-outbound-spans-b3.sh new file mode 100755 index 000000000..3cd009adb --- /dev/null +++ b/instrumentation/httpd/tests/08-create-outbound-spans-b3.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +TEST_NAME="Check that outbound span is created when forwarding request with mod_proxy (b3 single line header)" + +. tools.sh + +setup_test () { + +EXTRA_HTTPD_MODS="proxy proxy_http" + +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +OpenTelemetryPropagators b3 +OpenTelemetryIgnoreInbound Off + + + ProxyPass "/bar" "${ENDPOINT_PROXY}/hello-world" + ProxyPassReverse "/bar" "${ENDPOINT_PROXY}/hello-world" + +EOF + +} + +TRACE_ID="0abcdefabcdefabcdefabcdefabcdef0" +TRACE_SPAN="0123456789abcdef" +TRACE_FLAGS="1" + +HEADER_VAL="${TRACE_ID}-${TRACE_SPAN}-${TRACE_FLAGS}" +HEADER="b3: ${HEADER_VAL}" + +# this is what will be returned via proxy (this is called from netcat) +proxy () { + STATUS="200 OK" + + echo "---${REQ_METHOD}--- ${REQ_URL} "`date '+%H:%M:%S (%s)'` > /dev/tty + env | grep -i hdr > /dev/tty + echo '---------' > /dev/tty + + # extract everything before dash + RECV_SPAN=${HDR_B3%%-*} + + if [ "${RECV_SPAN}" != "${TRACE_ID}" ]; then + echo "Wrong header for b3 test" > /dev/tty + STATUS="401 Test failed" + fi + + echo -e "HTTP/1.1 ${STATUS}\r\nConnection: close\r\n\r\n" + + date + sleep 1 + echo "B3 Header was: ${HDR_B3}" + date +} + +run_test() { + curl --fail -v -H "${HEADER}" ${ENDPOINT_URL}/bar || failHttpd "Unable to download page with proxy enabled" +} + + +check_results() { + echo "Checking that exactly two spans were created (client one and server one)" + count '{' 2 # total two spans = one incoming and one outgoing + + echo "Checking that trace_id was properly parsed" + check 'trace_id' ${TRACE_ID} + + # there should be at least one span type client + grep "span kind : Client" ${OUTPUT_SPANS} || fail "Span kind client not found" + # there should be at least one span created (server one) with set parent_span_id as what we've sent + grep "parent_span_id: ${TRACE_SPAN}" ${OUTPUT_SPANS} || fail "Inbound propagation failed - not found parent span_id" + # now exclude it and find remaning one + PROXY_SPAN_ID=`grep -v "parent_span_id: ${TRACE_SPAN}" ${OUTPUT_SPANS} | grep "parent_span_id:" | cut -d ':' -f 2 || fail "Inbound propagation failed - not found parent span_id"` + # remaining parent_span_id should be span_id of the server one + grep "span_id :${PROXY_SPAN_ID}" ${OUTPUT_SPANS} || fail "Outbound propagation failed - it has not set proper parent span" +} + +run $@ diff --git a/instrumentation/httpd/tests/run-all.sh b/instrumentation/httpd/tests/run-all.sh new file mode 100755 index 000000000..308e01cd9 --- /dev/null +++ b/instrumentation/httpd/tests/run-all.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +ERR=0 + +for testfile in ??-*.sh; do + ./${testfile} || ERR=1 +done + +exit $ERR diff --git a/instrumentation/httpd/tests/setup-tools.sh b/instrumentation/httpd/tests/setup-tools.sh new file mode 100755 index 000000000..6c1b98853 --- /dev/null +++ b/instrumentation/httpd/tests/setup-tools.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +export DEBIAN_FRONTEND=noninteractive + +apt-get install -qq netcat-traditional httperf diff --git a/instrumentation/httpd/tests/tools.sh b/instrumentation/httpd/tests/tools.sh new file mode 100755 index 000000000..77e95f4a0 --- /dev/null +++ b/instrumentation/httpd/tests/tools.sh @@ -0,0 +1,189 @@ + +SCRIPT_NAME=$(basename "$0") + +HTTPD_CONFIG=${TEST_DIR_TMP}/extra-test-configuration.conf +HTTPD_ERRLOG=/var/log/apache2/error.log + +ENDPOINT_ADDR=127.0.0.1 +ENDPOINT_PORT=80 +ENDPOINT_URL=http://${ENDPOINT_ADDR} +PROXY_PORT=1500 +ENDPOINT_PROXY=http://127.0.0.1:${PROXY_PORT} + +CURL_TIMEOUT=2 # two seconds +CURL_CMD="curl --silent --show-error --fail -m ${CURL_TIMEOUT} -o /dev/null -v " + +OUTPUT_SPANS=/tmp/text-${SCRIPT_NAME}.spans + +EXTRA_HTTPD_MODS="" + +fail () { + printf 'FAIL: %s\n' "$1" >&2 ## Send message to stderr + echo "ERROR TEST FAILED" >&2 + exit "${2-1}" ## Return a code specified by $2 or 1 by default. +} + +failHttpd () { + printf 'FAIL: %s\n' "$1" >&2 ## Send message to stderr. Exclude >&2 if you don't want it that way. + echo "--- Below is httpd extra configuration used for this test with line numbers" + nl -b a ${HTTPD_CONFIG} + echo "--- Below is httpd last 10 entires from error log" + tail -n 10 ${HTTPD_ERRLOG} + echo "ERROR TEST FAILED" >&2 + exit "${2-1}" +} + +# count that given string occurs exactly n times +count() { + TOTAL=`grep -c "$1" ${OUTPUT_SPANS}` + if [ "$TOTAL" -ne $2 ]; then + echo "---" + cat ${OUTPUT_SPANS} + echo "---" + fail "Total number of $1 is not $2 but $TOTAL" + fi + echo OK Found $TOTAL occurence\(s\) of "$1" +} + +# returns one span field +getSpanField() { + WHICHONE=${2-1} + LINE=`grep -m ${WHICHONE} "$1" ${OUTPUT_SPANS} | tail -n 1` + VALUE=`echo $LINE | cut -d ':' -f 2-` + VALUE="${VALUE## }" + echo $VALUE +} + +# check that in span we have $1: $2 +check() { + WHICHONE=${3-1} + VALUE=`getSpanField "$1" ${WHICHONE}` + if [ "$VALUE" != "$2" ]; then + echo "---" + cat ${OUTPUT_SPANS} + echo "---" + echo $LINE + fail "Value for \"$1\" is not \"$2\" but \"$VALUE\"" + fi + echo OK - \"$1\" found with \"$2\" +} + +# this one should be redefined in each test +run_test() { + : +} + +# this one should be redefined in each test +check_results() { + : +} + +PROXY_PID_FILE=/tmp/proxy-listen-${PROXY_PORT} +NETCAT=/bin/nc.traditional + +start_proxy () { + echo $$ > ${PROXY_PID_FILE} + echo "Starting proxy on port ${PROXY_PORT} PID:$$ PID-File: ${PROXY_PID_FILE}" + # as long as pid file exists and contains our PID number handle incoming requests with netcat + while [ "`cat ${PROXY_PID_FILE} 2> /dev/null`" == "$$" ]; do + ${NETCAT} -w 1 -l -p ${PROXY_PORT} -c "$0 handle_proxy" 2>/dev/null + done + echo "Stopping proxy on port ${PROXY_PORT} PID:$$ PID-File: ${PROXY_PID_FILE}" +} + +stop_proxy () { + rm -rf ${PROXY_PID_FILE} +} + +# wrapper for netcat +handle_proxy_request () { + # read first HTTP line + read -t 0.1 REQUEST_LINE + REQ_URL=${REQUEST_LINE% *} + REQ_METHOD=${REQ_URL% *} + REQ_URL=${REQ_URL#* } + + # put all headers into env vars which start from HDR_ for simplicity + while read -t 0.1 HEADER + do + HEADER=${HEADER:0:-1} + if [ "$HEADER" = "" ]; then + break; + fi + HEADER_NAME=${HEADER%%:*} + HEADER_NAME=${HEADER_NAME//-} # User-Agent => UserAgent + HEADER_NAME=${HEADER_NAME^^} # UserAgent => USERAGENT + + HEADER_VALUE=${HEADER#*: } + export HDR_${HEADER_NAME}="${HEADER_VALUE}" + done + + # now call handler function + proxy +} + +# execute entire test +run() { + if [ "$1" == "start_proxy" ]; then + start_proxy + exit 0 + fi + if [ "$1" == "handle_proxy" ]; then + handle_proxy_request + exit 0 + fi + echo "------------------------------ Starting test $SCRIPT_NAME" + echo "----- Info: ${TEST_NAME}" + + apache2ctl stop + + # remove old spans (if any) + rm -rf ${OUTPUT_SPANS} + + setup_test + + # setup proxy if handler function was defined inside test script + if type proxy &>/dev/null ; then + rm -rf ${PROXY_PID_FILE} + $0 start_proxy & + trap stop_proxy EXIT + # wait for proxy to be started + echo "Waiting for $! to be ready" + while [ "`cat ${PROXY_PID_FILE} 2> /dev/null`" != "$!" ]; do sleep 0.1; done + echo "Proxy process already started" + fi + + # enable extra modules (if needed for this test) + if [ "${EXTRA_HTTPD_MODS}" != "" ]; then + a2enmod ${EXTRA_HTTPD_MODS} || failHttpd "Failed enable httpd mods ${EXTRA_HTTPD_MODS}" + fi + + # now check configuration + apache2ctl -t -c "Include ${HTTPD_CONFIG}" || failHttpd "Apache configtest failed" + + # run apache + apache2ctl -k start -c "Include ${HTTPD_CONFIG}" || failHttpd "Apache start failed" + + # now run test + run_test + + # stop apache - this is important as this flushes span file + apache2ctl -k stop -c "Include ${HTTPD_CONFIG}" || failHttpd "Apache stop failed" + + if type proxy &>/dev/null ; then + echo "Waiting for proxy to stop (PID $!)" + rm -rf ${PROXY_PID_FILE} + wait $! + fi + + # disable extra modules + if [ "${EXTRA_HTTPD_MODS}" != "" ]; then + # reverse list of modules so proxy proxy_http becomes proxy_http proxy + DISABLE_MODS=`echo ${EXTRA_HTTPD_MODS} | tac -s' '` + echo "Disabling mods ${DISABLE_MODS}" + a2dismod ${DISABLE_MODS} || failHttpd "Failed disable httpd mods ${EXTRA_HTTPD_MODS}" + fi + + # and check results + check_results && echo "Test ${SCRIPT_NAME} PASSED OK" +} diff --git a/instrumentation/httpd/tools/check-formatting.sh b/instrumentation/httpd/tools/check-formatting.sh new file mode 100755 index 000000000..fdffe4ea8 --- /dev/null +++ b/instrumentation/httpd/tools/check-formatting.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +ARGS="${1:-code build}" + +SELF=`dirname "$0"` + +print_help () { + echo "Checking code formatting" + echo "$0 [code] [build]" +} + +check_clang () { + ${SELF} ./format-code.sh +} + +check_buildifier () { + ${SELF} ./format-bazel.sh +} + +check_all () { + while [[ $# > 0 ]] ; do case "$1" in + code|clang) + check_clang + shift + ;; + build|buildifier) + check_buildifier + shift + ;; + -h) + print_help + exit 0 + ;; + *) + print_help + echo "Unknown argument: $1" >&2 + exit 1 + ;; + esac done +} + +check_all ${ARGS} diff --git a/instrumentation/httpd/tools/format-bazel.sh b/instrumentation/httpd/tools/format-bazel.sh new file mode 100755 index 000000000..4e22f8530 --- /dev/null +++ b/instrumentation/httpd/tools/format-bazel.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# directory one level up from this script directory +PROJECT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd ) + +if ! ${BUILDIFIER:=buildifier} -version ; then + echo ${BUILDIFIER} not found in '$PATH' = $PATH >&2 + exit 1 +fi + + +if [ "$1" == "-i" ]; then # apply + MODE="-mode fix -v" + shift +else + MODE="-mode diff" +fi + +if [[ "$#" -gt 0 && -d "$1" ]]; then + PROJECT_DIR="$1" + shift +fi + +SRC_FILES=${1:-$(find "$PROJECT_DIR" -path '*/.*' -prune -name WORKSPACE -print -o -name BUILD -print -o \ + -name '*.BUILD' -o -name '*.bzl' -print)} + +${BUILDIFIER} ${MODE} ${SRC_FILES} + +BADFORMAT=$? +if [ "$BADFORMAT" != "0" ]; then + echo >&2 + echo "There are some files with broken formating, please run following command to fix all of them:" >&2 + echo "$0 -i" >&2 + exit 1 +fi diff --git a/instrumentation/httpd/tools/format-code.sh b/instrumentation/httpd/tools/format-code.sh new file mode 100755 index 000000000..1617806a5 --- /dev/null +++ b/instrumentation/httpd/tools/format-code.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# directory one level up from this script directory +PROJECT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd ) + +APPLY="" + +if [ "$1" == "-i" ]; then # apply + APPLY="-i" + shift +fi + +if [[ "$#" -gt 0 && -d "$1" ]]; then + PROJECT_DIR="$1" + shift +fi + +SRC_FILES=${1:-$(find "$PROJECT_DIR" -path '.*' -prune -name '*.c*' -o -name '*.h')} + +if ! ${CLANG_FORMAT:=clang-format} --version ; then + echo ${CLANG_FORMAT} not found in '$PATH' = $PATH >&2 + exit 1 +fi + +if [ "$APPLY" == "-i" ]; then + ${CLANG_FORMAT} --verbose --style=file -i ${SRC_FILES} + exit $? +fi + +# show formatting problems +EXITCODE=0 +for FILE in $SRC_FILES; do + ${CLANG_FORMAT} --style=file "${FILE}" | git diff --no-index -- "${FILE}" - + BADFORMAT=$? + if [ "$BADFORMAT" != "0" ]; then + EXITCODE=1 + fi +done + +if [ "$EXITCODE" != "0" ]; then + echo >&2 + echo "There are some files with broken formating, please run following command to fix all of them:" >&2 + echo "$0 -i" >&2 +fi + +exit ${EXITCODE} diff --git a/instrumentation/httpd/tools/setup-tools.sh b/instrumentation/httpd/tools/setup-tools.sh new file mode 100755 index 000000000..4f11063df --- /dev/null +++ b/instrumentation/httpd/tools/setup-tools.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +BUILDIFIER_VERSION=3.5.0 + +ARGS="${1:-code build}" + +print_help () { + echo "Installs tools for checking code formatting" + echo "$0 [code] [build]" +} + +setup_clang () { + export DEBIAN_FRONTEND=noninteractive + apt-get update -y + apt-get install -qq git + apt-get install -qq clang-format +} + +setup_buildifier () { + apt-get install -qq curl + curl -L -o /usr/local/bin/buildifier https://github.com/bazelbuild/buildtools/releases/download/${BUILDIFIER_VERSION}/buildifier + chmod +x /usr/local/bin/buildifier +} + +setup_all () { + while [[ $# > 0 ]] ; do case "$1" in + code|clang) + setup_clang + shift + ;; + build|buildifier) + setup_buildifier + shift + ;; + -h) + print_help + exit 0 + ;; + *) + print_help + echo "Unknown argument: $1" >&2 + exit 1 + ;; + esac done +} + +setup_all ${ARGS}