├── .dockerignore ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── continuous-build.yml │ ├── continuous-monitoring.yml │ └── release-build-publish.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.amazonlinux ├── LICENSE ├── NOTICE ├── README.md ├── THIRD-PARTY-LICENSES.txt ├── Tool └── src │ ├── build-in-docker.sh │ └── packaging │ ├── build-package-legacy.sh │ ├── debian │ ├── build_deb_linux.sh │ ├── changelog │ ├── changelog.Debian │ ├── conffiles │ ├── control │ ├── debian-binary │ ├── lintian-overrides │ ├── postinst │ ├── preinst │ ├── prerm │ ├── xray.conf │ └── xray.service │ ├── linux │ ├── README.md │ ├── build_rpm_linux.sh │ ├── build_zip_linux.sh │ ├── xray.conf │ ├── xray.service │ └── xray.spec │ ├── major-version-packages.sh │ ├── osx │ └── build_zip_osx.sh │ ├── sign-packages.sh │ └── windows │ └── build_zip_win.sh ├── cmd └── tracing │ ├── daemon.go │ ├── tracing.go │ └── tracing_windows.go ├── go.mod ├── go.sum ├── makefile ├── pkg ├── bufferpool │ ├── bufferpool.go │ └── bufferpool_test.go ├── cfg.yaml ├── cfg │ ├── cfg.go │ └── cfg_test.go ├── cli │ ├── cli.go │ └── cli_test.go ├── conn │ ├── conn.go │ ├── conn_test.go │ └── xray_client.go ├── logger │ ├── log_config.go │ └── logger_test.go ├── processor │ ├── batchprocessor.go │ ├── batchprocessor_test.go │ ├── processor.go │ └── processor_test.go ├── profiler │ └── profiler.go ├── proxy │ ├── server.go │ └── server_test.go ├── ringbuffer │ ├── ringbuffer.go │ └── ringbuffer_test.go ├── socketconn │ ├── socketconn.go │ └── udp │ │ └── udp.go ├── telemetry │ ├── telemetry.go │ └── telemetry_test.go ├── tracesegment │ ├── tracesegment.go │ ├── tracesegment_test.go │ └── tracesegment_test_util.go └── util │ ├── test │ ├── log_writer.go │ ├── mock_timer_client.go │ └── mock_timer_client_test.go │ ├── timer │ └── timer.go │ ├── util.go │ └── util_test.go └── testing ├── README.md ├── generate_trace_data.sh └── terraform ├── amis.tf ├── main.tf ├── testcases ├── linux_deb.tfvars ├── linux_rpm.tfvars ├── linux_zip.tfvars ├── linux_zip_cn.tfvars └── linux_zip_us_gov.tfvars └── variables.tf /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .dockerignore 3 | Dockerfile 4 | 5 | *.md 6 | 7 | bin 8 | test_coverage 9 | .idea 10 | .DS_Store 11 | 12 | # Binaries for programs and plugins 13 | *.exe 14 | *.exe~ 15 | *.dll 16 | *.so 17 | *.dylib 18 | 19 | # Test binary, build with `go test -c` 20 | *.test 21 | 22 | # Output of the go coverage tool, specifically when used with LiteIDE 23 | *.out 24 | 25 | vendor 26 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | ##################################################### 2 | # 3 | # List of approvers for this repository 4 | # 5 | ##################################################### 6 | # 7 | # Learn about CODEOWNERS file format: 8 | # https://help.github.com/en/articles/about-code-owners 9 | # 10 | 11 | * @aws/aws-x-ray 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Limit to only `issues` or `pulls` 6 | only: issues 7 | # Issues with these labels will never be considered stale 8 | exemptLabels: 9 | - pinned 10 | - bug 11 | - enhancement 12 | - feature-request 13 | - help wanted 14 | - work-in-progress 15 | - pending release 16 | # Label to use when marking an issue as stale 17 | staleLabel: stale 18 | # Comment to post when marking an issue as stale. Set to `false` to disable 19 | markComment: > 20 | This issue has been automatically marked as stale because it has not had 21 | recent activity. It will be closed if no further activity occurs in next 7 days. Thank you 22 | for your contributions. 23 | # Comment to post when closing a stale issue. Set to `false` to disable 24 | closeComment: false 25 | -------------------------------------------------------------------------------- /.github/workflows/continuous-build.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Build and Publish 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | workflow_dispatch: 10 | inputs: 11 | region: 12 | description: The region to test the daemon binaries in. 13 | required: true 14 | default: us-west-2 15 | permissions: 16 | id-token: write 17 | contents: read 18 | jobs: 19 | build: 20 | name: Build on ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | os: 24 | - macos-latest 25 | - ubuntu-latest 26 | - windows-latest 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - name: Checkout Repository 30 | uses: actions/checkout@v2 31 | 32 | - name: Setup Go 33 | uses: actions/setup-go@v2 34 | with: 35 | go-version: '^1.24.2' 36 | 37 | - uses: actions/cache@v4 38 | with: 39 | path: ~/go/pkg/mod 40 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 41 | restore-keys: | 42 | ${{ runner.os }}-go- 43 | 44 | - name: Build archives and test 45 | run: make build test 46 | env: 47 | VERSION: 0.${{ github.sha }} # debian package requires version to start with a digit 48 | 49 | - name: Build linux archives 50 | if: ${{ runner.os == 'Linux' }} 51 | run: make packaging 52 | env: 53 | VERSION: 0.${{ github.sha }} # debian package requires version to start with a digit 54 | 55 | - name: Configure AWS Credentials 56 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 57 | uses: aws-actions/configure-aws-credentials@v1 58 | with: 59 | role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN_RELEASE }} 60 | aws-region: us-east-1 61 | 62 | - name: Verify Daemon binary 63 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 64 | run: | 65 | build/xray-linux-amd64/xray -o -l dev & 66 | sleep 10 67 | echo -e '{"format":"json","version":1}\nx' | nc -w 1 -u 127.0.0.1 2000 68 | sleep 10 69 | ProcNumber=$(ps -ef |grep -w xray|grep -v grep|wc -l) 70 | echo $ProcNumber 71 | if [[ $ProcNumber == 0 ]]; then 72 | exit 1 73 | fi 74 | 75 | - name: Download package signing GPG secret key 76 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 77 | run: | 78 | aws secretsmanager get-secret-value --region us-east-1 --secret-id "XRayDaemonSecrets" | jq -r ".SecretString" > aws-xray-secret.gpg 79 | md5sum aws-xray-secret.gpg 80 | 81 | - name: Import signing GPG key 82 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 83 | run: | 84 | gpg --import aws-xray-secret.gpg 85 | gpg --list-keys 86 | gpg --armor --export -a "aws-xray@amazon.com" > aws-xray-public.gpg 87 | rpm --import aws-xray-public.gpg 88 | shred -fuvz aws-xray-secret.gpg 89 | shred -fuvz aws-xray-public.gpg 90 | 91 | - name: Sign daemon packages 92 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 93 | run: ./Tool/src/packaging/sign-packages.sh 94 | 95 | - name: Remove Package Signing GPG Key from local GPG Key Ring 96 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 97 | run: | 98 | gpg --fingerprint --with-colons aws-xray@amazon.com | grep -m 1 "fpr" | sed -n 's/^fpr:::::::::\([[:alnum:]]\+\):/\1/p' | xargs gpg --batch --yes --delete-secret-keys 99 | gpg --list-secret-keys 100 | 101 | - name: Upload archives as actions artifact 102 | if: ${{ runner.os == 'Linux' }} 103 | uses: actions/upload-artifact@v4 104 | with: 105 | name: distributions 106 | path: build/dist/ 107 | 108 | - name: Login to Public ECR 109 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 110 | run: aws ecr-public get-login-password | docker login --username AWS --password-stdin public.ecr.aws 111 | 112 | - name: Login to DockerHub 113 | if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} 114 | uses: docker/login-action@v1 115 | with: 116 | username: ${{ secrets.DOCKER_USER }} 117 | password: ${{ secrets.DOCKER_PASS }} 118 | 119 | - name: Set up Docker Buildx 120 | if: ${{ runner.os == 'Linux' }} 121 | uses: docker/setup-buildx-action@v1 122 | 123 | - name: Cache Docker layers 124 | uses: actions/cache@v4 125 | with: 126 | path: /tmp/.buildx-cache 127 | key: ${{ runner.os }}-buildx-${{ github.sha }} 128 | restore-keys: | 129 | ${{ runner.os }}-buildx- 130 | 131 | - name: Build docker image for public ECR 132 | if: ${{ runner.os == 'Linux' }} 133 | uses: docker/build-push-action@v2 134 | with: 135 | context: . 136 | file: ./Dockerfile 137 | push: ${{ github.event_name == 'push' }} 138 | platforms: linux/amd64,linux/arm64 139 | tags: | 140 | public.ecr.aws/xray/aws-xray-daemon:alpha 141 | cache-from: type=local,src=/tmp/.buildx-cache 142 | cache-to: type=local,dest=/tmp/.buildx-cache 143 | 144 | - name: Build docker image for DockerHub 145 | if: ${{ runner.os == 'Linux' }} 146 | uses: docker/build-push-action@v2 147 | with: 148 | context: . 149 | file: ./Dockerfile.amazonlinux 150 | push: ${{ github.event_name == 'push' }} 151 | platforms: linux/amd64,linux/arm64 152 | tags: | 153 | amazon/aws-xray-daemon:alpha 154 | cache-from: type=local,src=/tmp/.buildx-cache 155 | cache-to: type=local,dest=/tmp/.buildx-cache 156 | 157 | test: 158 | name: Integration testing 159 | if: ${{ github.event_name != 'pull_request' }} #Cannot run integration tests on PR as there is no access to the IAM Role via GH secrets. 160 | needs: [build] 161 | runs-on: ubuntu-latest 162 | strategy: 163 | fail-fast: false 164 | matrix: 165 | testcases: 166 | - linux_deb 167 | - linux_rpm 168 | - linux_zip 169 | - linux_zip_cn 170 | - linux_zip_us_gov 171 | 172 | steps: 173 | - name: Checkout repo 174 | uses: actions/checkout@v2 175 | 176 | - name: Download build artifacts 177 | uses: actions/download-artifact@v4 178 | with: 179 | name: distributions 180 | path: distributions 181 | 182 | - name: Display structure of downloaded files 183 | run: ls -R 184 | 185 | - name: Set up terraform 186 | uses: hashicorp/setup-terraform@v2 187 | 188 | # workaround for handling input value for auto-triggered workflows 189 | # https://dev.to/mrmike/github-action-handling-input-default-value-5f2g 190 | - name: Set AWS region and partition values 191 | id: setRegion 192 | run: | 193 | if [[ ${{ matrix.testcases }} == "linux_zip_cn" ]]; then 194 | echo "::set-output name=region::cn-north-1" 195 | echo "::set-output name=partition::aws-cn" 196 | elif [[ ${{ matrix.testcases }} == "linux_zip_us_gov" ]]; then 197 | echo "::set-output name=region::us-gov-west-1" 198 | echo "::set-output name=partition::aws-us-gov" 199 | else 200 | USER_INPUT=${{ github.event.inputs.region }} 201 | # Use user input region or fall back to "us-west-2" 202 | REGION=${USER_INPUT:-"us-west-2"} 203 | echo "::set-output name=region::$REGION" 204 | echo "::set-output name=partition::aws" 205 | fi 206 | 207 | - name: Configure AWS Credentials 208 | if: ${{ steps.setRegion.outputs.partition == 'aws' }} 209 | uses: aws-actions/configure-aws-credentials@v1 210 | with: 211 | role-to-assume: ${{ secrets.TESTING_ROLE_ARN }} 212 | aws-region: ${{ steps.setRegion.outputs.region }} 213 | 214 | - name: Configure AWS Credentials - China 215 | if: ${{ steps.setRegion.outputs.partition == 'aws-cn' }} 216 | uses: aws-actions/configure-aws-credentials@v1 217 | with: 218 | role-to-assume: ${{ secrets.TESTING_ROLE_ARN_CN }} 219 | aws-region: cn-north-1 220 | 221 | - name: Configure AWS Credentials - US Gov 222 | if: ${{ steps.setRegion.outputs.partition == 'aws-us-gov' }} 223 | uses: aws-actions/configure-aws-credentials@v1 224 | with: 225 | role-to-assume: ${{ secrets.TESTING_ROLE_ARN_US_GOV }} 226 | aws-region: us-gov-west-1 227 | 228 | - name: Generate trace data 229 | id: traceData 230 | run: | 231 | sudo chmod +x ./testing/generate_trace_data.sh 232 | ./testing/generate_trace_data.sh 233 | 234 | - name: Run terraform and send trace data 235 | id: terraformSendData 236 | run: | 237 | cd testing/terraform 238 | terraform init 239 | terraform validate 240 | terraform apply -auto-approve -var-file=./testcases/${{ matrix.testcases }}.tfvars -var=aws_region=${{ steps.setRegion.outputs.region }} 241 | 242 | - name: Validate trace data 243 | if: steps.terraformSendData.outcome == 'success' 244 | run: | 245 | echo "trace-id: ${{ env.TRACE_ID }}" 246 | echo "sleeping for 60 seconds" 247 | for i in {1..60}; do echo 'Sleeping...'$i && sleep 1; done 248 | var=$(aws xray batch-get-traces --trace-ids ${{ env.TRACE_ID }} --region ${{ steps.setRegion.outputs.region }} | jq -r '.Traces[0].Id') 249 | 250 | if [[ $var == ${{ env.TRACE_ID }} ]]; then 251 | echo "Found matching trace." 252 | else 253 | echo "Trace not found" 254 | exit 1 255 | fi 256 | 257 | - name: Terraform destroy 258 | if: always() 259 | run: | 260 | cd testing/terraform 261 | terraform destroy -auto-approve -var=aws_region=${{ steps.setRegion.outputs.region }} 262 | -------------------------------------------------------------------------------- /.github/workflows/continuous-monitoring.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Monitoring of Dockerhub and public ECR 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '*/10 * * * *' 6 | 7 | permissions: 8 | id-token: write 9 | contents: read 10 | 11 | jobs: 12 | monitor-ecr: 13 | name: Monitor public ECR 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Configure AWS Credentials 17 | uses: aws-actions/configure-aws-credentials@v4 18 | with: 19 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 20 | aws-region: us-east-1 21 | 22 | - name: Login to Public ECR 23 | uses: docker/login-action@v1 24 | with: 25 | registry: public.ecr.aws 26 | 27 | - name: Pull an image from public ECR 28 | id: pull-from-ecr 29 | run: docker pull public.ecr.aws/xray/aws-xray-daemon:latest 30 | 31 | - name: Publish metric on pulling an image 32 | if: ${{ always() }} 33 | run: | 34 | if [[ "${{ steps.pull-from-ecr.outcome }}" == "failure" ]]; then 35 | aws cloudwatch put-metric-data --metric-name PullImageFailureFromECR --dimensions failure=rate --namespace MonitorDaemon --value 1 --timestamp $(date +%s) 36 | else 37 | aws cloudwatch put-metric-data --metric-name PullImageFailureFromECR --dimensions failure=rate --namespace MonitorDaemon --value 0 --timestamp $(date +%s) 38 | fi 39 | 40 | - name: Run daemon image 41 | run: | 42 | docker run --name xray-daemon public.ecr.aws/xray/aws-xray-daemon:latest -o -n us-west-2 & 43 | sleep 30 44 | 45 | - name: Publish metrics on daemon startup 46 | if: ${{ always() }} 47 | run: | 48 | if [[ "$(docker container inspect -f '{{.State.Status}}' xray-daemon )" != "running" ]]; then 49 | aws cloudwatch put-metric-data --metric-name DaemonImageStartupFailureFromECR --dimensions failure=rate --namespace MonitorDaemon --value 1 --timestamp $(date +%s) 50 | else 51 | aws cloudwatch put-metric-data --metric-name DaemonImageStartupFailureFromECR --dimensions failure=rate --namespace MonitorDaemon --value 0 --timestamp $(date +%s) 52 | fi 53 | 54 | monitor-dockerhub: 55 | name: Monitor docker hub 56 | runs-on: ubuntu-latest 57 | steps: 58 | - name: Configure AWS Credentials 59 | uses: aws-actions/configure-aws-credentials@v4 60 | with: 61 | role-to-assume: ${{ secrets.AWS_INTEG_TEST_ROLE_ARN }} 62 | aws-region: us-east-1 63 | 64 | - name: Pull an image from docker hub 65 | id: pull-from-dockerhub 66 | run: docker pull amazon/aws-xray-daemon:latest 67 | 68 | - name: Publish metric on pulling an image 69 | if: ${{ always() }} 70 | run: | 71 | if [[ "${{ steps.pull-from-dockerhub.outcome }}" == "failure" ]]; then 72 | aws cloudwatch put-metric-data --metric-name PullImageFailureFromDockerhub --dimensions failure=rate --namespace MonitorDaemon --value 1 --timestamp $(date +%s) 73 | else 74 | aws cloudwatch put-metric-data --metric-name PullImageFailureFromDockerhub --dimensions failure=rate --namespace MonitorDaemon --value 0 --timestamp $(date +%s) 75 | fi 76 | 77 | - name: Run daemon image 78 | run: | 79 | docker run --name xray-daemon amazon/aws-xray-daemon:latest -o -n us-west-2 & 80 | sleep 30 81 | 82 | - name: Publish metrics on daemon startup 83 | if: ${{ always() }} 84 | run: | 85 | if [[ "$(docker container inspect -f '{{.State.Status}}' xray-daemon )" != "running" ]]; then 86 | aws cloudwatch put-metric-data --metric-name DaemonImageStartupFailureFromDockerhub --dimensions failure=rate --namespace MonitorDaemon --value 1 --timestamp $(date +%s) 87 | else 88 | aws cloudwatch put-metric-data --metric-name DaemonImageStartupFailureFromDockerhub --dimensions failure=rate --namespace MonitorDaemon --value 0 --timestamp $(date +%s) 89 | fi 90 | -------------------------------------------------------------------------------- /.github/workflows/release-build-publish.yml: -------------------------------------------------------------------------------- 1 | name: Release build and publish 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: The version to tag the release with, e.g., 1.2.0, 1.2.1-alpha.1 7 | required: true 8 | major_version: 9 | description: The major version to tag the release with, e.g., 1.x, 2.x, 3.x 10 | required: true 11 | permissions: 12 | id-token: write 13 | contents: write 14 | jobs: 15 | build_publish_daemon_image: 16 | name: Build X-Ray daemon artifacts and publish docker image to docker hub and public ECR 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v2 21 | 22 | - name: Setup Go 23 | uses: actions/setup-go@v2 24 | with: 25 | go-version: '^1.24.2' 26 | 27 | - uses: actions/cache@v2 28 | with: 29 | path: ~/go/pkg/mod 30 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 31 | restore-keys: | 32 | ${{ runner.os }}-go- 33 | 34 | - name: Build archives and test 35 | run: make build test 36 | env: 37 | VERSION: ${{ github.event.inputs.version }} 38 | 39 | - name: Build Linux archives 40 | run: make packaging 41 | env: 42 | VERSION: ${{ github.event.inputs.version }} 43 | 44 | - name: Configure AWS Credentials 45 | uses: aws-actions/configure-aws-credentials@v1 46 | with: 47 | role-to-assume: ${{ secrets.AWS_ASSUME_ROLE_ARN_RELEASE }} 48 | aws-region: us-east-1 49 | 50 | - name: Download package signing GPG secret key 51 | run: | 52 | aws secretsmanager get-secret-value --region us-east-1 --secret-id "XRayDaemonSecrets" | jq -r ".SecretString" > aws-xray-secret.gpg 53 | md5sum aws-xray-secret.gpg 54 | 55 | - name: Import signing GPG key 56 | run: | 57 | gpg --import aws-xray-secret.gpg 58 | gpg --list-keys 59 | gpg --armor --export -a "aws-xray@amazon.com" > aws-xray-public.gpg 60 | rpm --import aws-xray-public.gpg 61 | shred -fuvz aws-xray-secret.gpg 62 | shred -fuvz aws-xray-public.gpg 63 | 64 | - name: Sign daemon packages 65 | run: ./Tool/src/packaging/sign-packages.sh 66 | 67 | - name: Remove Package Signing GPG Key from local GPG Key Ring 68 | run: | 69 | gpg --fingerprint --with-colons aws-xray@amazon.com | grep -m 1 "fpr" | sed -n 's/^fpr:::::::::\([[:alnum:]]\+\):/\1/p' | xargs gpg --batch --yes --delete-secret-keys 70 | gpg --list-secret-keys 71 | 72 | - name: Upload archives as actions artifact 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: distributions 76 | path: build/dist/ 77 | 78 | - name: Login to Public ECR 79 | run: aws ecr-public get-login-password | docker login --username AWS --password-stdin public.ecr.aws 80 | 81 | - name: Login to DockerHub 82 | uses: docker/login-action@v1 83 | with: 84 | username: ${{ secrets.DOCKER_USER }} 85 | password: ${{ secrets.DOCKER_PASS }} 86 | 87 | - name: Set up Docker Buildx 88 | uses: docker/setup-buildx-action@v1 89 | 90 | - name: Cache Docker layers 91 | uses: actions/cache@v2 92 | with: 93 | path: /tmp/.buildx-cache 94 | key: ${{ runner.os }}-buildx-${{ github.sha }} 95 | restore-keys: | 96 | ${{ runner.os }}-buildx- 97 | 98 | - name: Build and push docker image for Public ECR 99 | uses: docker/build-push-action@v2 100 | with: 101 | context: . 102 | file: ./Dockerfile 103 | platforms: linux/amd64,linux/arm64 104 | tags: | 105 | public.ecr.aws/xray/aws-xray-daemon:${{ github.event.inputs.version }} 106 | public.ecr.aws/xray/aws-xray-daemon:latest 107 | public.ecr.aws/xray/aws-xray-daemon:${{ github.event.inputs.major_version }} 108 | push: true 109 | cache-from: type=local,src=/tmp/.buildx-cache 110 | cache-to: type=local,dest=/tmp/.buildx-cache 111 | 112 | - name: Build and push docker image for DockerHub 113 | uses: docker/build-push-action@v2 114 | with: 115 | context: . 116 | file: ./Dockerfile.amazonlinux 117 | platforms: linux/amd64,linux/arm64 118 | tags: | 119 | amazon/aws-xray-daemon:${{ github.event.inputs.version }} 120 | amazon/aws-xray-daemon:latest 121 | amazon/aws-xray-daemon:${{ github.event.inputs.major_version }} 122 | push: true 123 | cache-from: type=local,src=/tmp/.buildx-cache 124 | cache-to: type=local,dest=/tmp/.buildx-cache 125 | 126 | - name: Create Release 127 | id: create_release 128 | uses: actions/create-release@v1 129 | env: 130 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 131 | with: 132 | tag_name: 'v${{ github.event.inputs.version }}' 133 | release_name: 'AWS X-Ray daemon update v${{ github.event.inputs.version }}' 134 | body: 'Please refer [change-log](https://github.com/aws/aws-xray-daemon/blob/master/CHANGELOG.md) for more details' 135 | draft: true 136 | prerelease: false 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | test_coverage 3 | build 4 | .idea 5 | .DS_Store 6 | 7 | # Binaries for programs and plugins 8 | *.exe 9 | *.exe~ 10 | *.dll 11 | *.so 12 | *.dylib 13 | 14 | # Test binary, build with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | vendor 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## 3.3.14 (2025-02-12) 5 | - Bump Go version to v1.23.6 [PR #231](https://github.com/aws/aws-xray-daemon/pull/231) 6 | - Bump golang.org/x/net from 0.23.0 to 0.33.0 [PR #230](https://github.com/aws/aws-xray-daemon/pull/230) 7 | 8 | ## 3.3.13 (2024-07-24) 9 | - Bump Go version to v1.22.4 [PR #222](https://github.com/aws/aws-xray-daemon/pull/222) 10 | 11 | ## 3.3.12 (2024-05-01) 12 | - Bump golang.org/x/net from 0.17.0 to 0.23.0 [PR #219](https://github.com/aws/aws-xray-daemon/pull/219) 13 | 14 | ## 3.3.11 (2024-03-27) 15 | - Use latest tag for amazonlinux for Docker image [PR #217](https://github.com/aws/aws-xray-daemon/pull/217) 16 | - Add http2 timouts to close bad TCP connection [PR #216](https://github.com/aws/aws-xray-daemon/pull/216) 17 | 18 | ## 3.3.10 (2023-12-20) 19 | - Bump Go version to v1.21.5 [PR #212](https://github.com/aws/aws-xray-daemon/pull/212) 20 | 21 | ## 3.3.9 (2023-10-31) 22 | - Bump golang.org/x/net to v0.17.0 to fix CVE-2023-44487 [PR #208](https://github.com/aws/aws-xray-daemon/pull/208) 23 | - Bump Go version to v1.21.3 [PR #209](https://github.com/aws/aws-xray-daemon/pull/209) 24 | 25 | ## 3.3.8 (2023-09-08) 26 | - Bump Go version to v1.21.1 [PR #205](https://github.com/aws/aws-xray-daemon/pull/205) 27 | - Bump golang.org/x/net to v0.15.0 to fix CVE-2023-3978 [PR #205](https://github.com/aws/aws-xray-daemon/pull/205) 28 | - Bump aws-sdk-go to v1.44.298 for SSO token provider support for sso-session in AWS shared config [PR #206](https://github.com/aws/aws-xray-daemon/pull/206) 29 | 30 | ## 3.3.7 (2023-04-24) 31 | - Bump golang.org/x/net to v0.7.0 to fix CVE 2022-41725 [PR #193](https://github.com/aws/aws-xray-daemon/pull/193) 32 | - Bump Go version to 1.20.3 [PR #196](https://github.com/aws/aws-xray-daemon/pull/196) 33 | 34 | ## 3.3.6 (2023-02-01) 35 | - User-agent redesign - add additional information to user-agent [PR #188](https://github.com/aws/aws-xray-daemon/pull/188) 36 | - Remove custom backoff logic for sending segments [PR #186](https://github.com/aws/aws-xray-daemon/pull/186) 37 | 38 | ## 3.3.5 (2022-09-22) 39 | - Fix CVE-2022-27664 [PR #180](https://github.com/aws/aws-xray-daemon/pull/180) 40 | 41 | ## 3.3.4 (2022-08-31) 42 | - Upgrade aws-sdk-go to latest version to get SSO credential support [PR #168](https://github.com/aws/aws-xray-daemon/pull/168) 43 | - Fix CVE issues by bumping GO version to 1.18 [PR #173](https://github.com/aws/aws-xray-daemon/pull/173) 44 | 45 | ## 3.3.3 (2021-07-21) 46 | - Add logging for ignored errors [PR #138](https://github.com/aws/aws-xray-daemon/pull/138) 47 | - Upgrade minimum golang version for compiling to 1.16.6 [PR #148](https://github.com/aws/aws-xray-daemon/pull/148) 48 | - Upgrade golang `net` and `sys` libraries [PR #150](https://github.com/aws/aws-xray-daemon/pull/150) 49 | 50 | ## 3.3.2 (2021-04-16) 51 | - Fix Daemon startup log missing, set default log level as Info [PR #129](https://github.com/aws/aws-xray-daemon/pull/129) 52 | - Rollback Dockerhub image base to AmazonLinux [PR #130](https://github.com/aws/aws-xray-daemon/pull/130) 53 | 54 | ## 3.3.1 (2021-04-13) 55 | - Fix nil pointer dereference when the daemon receives an invalid message [PR #122](https://github.com/aws/aws-xray-daemon/pull/122) 56 | 57 | ## 3.3.0 (2021-04-13) 58 | - Support for fetching region from ECS metadata [PR #41](https://github.com/aws/aws-xray-daemon/pull/41) 59 | - Building X-Ray Daemon docker image from `scratch` instead of from `amazonlinux` as done previously [PR #44](https://github.com/aws/aws-xray-daemon/pull/44) 60 | - Added license and third party licenses to all the packages [PR #46](https://github.com/aws/aws-xray-daemon/pull/46) 61 | - Set hostname and instance-id when EC2 metadata is blocked [PR #54](https://github.com/aws/aws-xray-daemon/pull/54) 62 | - Prevent leaking customer traces at default log level [PR #61](https://github.com/aws/aws-xray-daemon/pull/61) 63 | - Do not print successfully if there are Unprocessed segments [PR #64](https://github.com/aws/aws-xray-daemon/pull/64) 64 | - Reduce debug logging causing log flooding [PR #65](https://github.com/aws/aws-xray-daemon/pull/65) 65 | - Debug logging print only unprocessed segments detail [PR #66](https://github.com/aws/aws-xray-daemon/pull/66) 66 | - Change linux service type from `simple` to `exec` [PR #98](https://github.com/aws/aws-xray-daemon/pull/98) 67 | - Log traceId for unprocessed segments [PR #106](https://github.com/aws/aws-xray-daemon/pull/106) 68 | 69 | ## 3.2.0 (2019-11-26) 70 | - Do not fail when cleaning build folder 71 | - Bumping up aws-sdk-go to enable IAM Roles for Service Account support in Kubernetes deployments 72 | - xray.service: explicitly set ConfigurationDirectoryMode to 0755 73 | 74 | ## 3.1.0 (2019-07-01) 75 | - STS credentials are fetched from STS regional endpoint instead of global endpoint. If the configured region is disabled for the STS token, the credentials are 76 | fetched from primary regional endpoint in that AWS partition. The primary regional endpoint cannot be disabled for STS token. 77 | - Updated AWS Go dependency to version 1.20.10 78 | - Added debug log on request received for HTTP proxy server 79 | - Added info log for the X-Ray service endpoint used by HTTP Proxy server 80 | - Updated X-Ray service endpoint resolution for HTTP Proxy server 81 | 82 | ## 3.0.2 (2019-06-19) 83 | - Reconfiguring ring buffer channel size to number of buffers allocated to the daemon instead of fix 250 traces 84 | 85 | ## 3.0.1 (2019-04-16) 86 | - Removed allocating 64KB size to UDP socket connection. Now, the daemon relies on underlying OS default UDP receiver size for the UDP socket connection 87 | - Updated readme about credential configuration: [PR #21](https://github.com/aws/aws-xray-daemon/pull/21) 88 | 89 | ## 3.0.0 (2018-08-28) 90 | - The daemon now serves as a proxy to the X-Ray SDK for API calls that are related to sampling rules. The proxy runs default on TCP port 2000 and relays calls to get sampling rules and report sampling statistics to X-Ray. The TCP proxy server address can be configured from command line using `t` flag or by using `cfg.yaml` version `2` file. 91 | - `cfg.yaml` file version is changed to `2` and has an extra attribute. The daemon supports version `1` of cfg.yaml: 92 | 93 | ``` 94 | Socket: 95 | # Change the address and port on which the daemon listens for HTTP requests to proxy to AWS X-Ray. 96 | TCPAddress: "127.0.0.1:2000" 97 | ``` 98 | - Adds timestamp header `X-Amzn-Xray-Timestamp` to PutTraceSegments API calls made by the daemon 99 | - Adding support for configuring `ProxyAddress` through command line: PR [#10](https://github.com/aws/aws-xray-daemon/pull/10) 100 | - AWS X-Ray Daemon source code supports Go Version 1.8 and later 101 | 102 | 103 | ## 2.1.2 (2018-05-14) 104 | - SystemD service file updates for Debian and Linux binaries: PR [#3](https://github.com/aws/aws-xray-daemon/pull/3) 105 | - Added Travis CI: PR [#7](https://github.com/aws/aws-xray-daemon/pull/7) 106 | - Updated service spec files for debian and linux binaries to wait for network to be available : PR [#6](https://github.com/aws/aws-xray-daemon/pull/6) 107 | - Added more unit tests for `conn` package 108 | 109 | ## 2.1.1 (2018-04-25) 110 | - This version of the AWS X-Ray daemon fixes an issue 111 | where the daemon overrides the customer provided IAM role ARN configuration with the IAM role assigned to EC2 instance profile if a region wasn’t specified in the configuration. We recommend updating to the latest version at the earliest. The latest version of the daemon is available at https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon.html 112 | 113 | ## 2.1.0 (2018-03-08) 114 | - Open sourced the X-Ray daemon project 115 | - To not upload telemetry data if no traces are recorded 116 | - The daemon logs error to stderr if it fails to read provided configuration file 117 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-xray-daemon/issues), or [recently closed](https://github.com/aws/aws-xray-daemon/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-xray-daemon/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/aws-xray-daemon/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM --platform=$BUILDPLATFORM golang:1.24.2-alpine AS build-env 3 | ARG TARGETPLATFORM 4 | 5 | RUN apk update && apk add ca-certificates 6 | 7 | WORKDIR /workspace 8 | 9 | COPY . . 10 | 11 | RUN adduser -D -u 10001 xray 12 | ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64} 13 | RUN Tool/src/build-in-docker.sh 14 | 15 | FROM scratch 16 | COPY --from=build-env /workspace/xray . 17 | COPY --from=build-env /etc/passwd /etc/passwd 18 | COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 19 | COPY pkg/cfg.yaml /etc/amazon/xray/cfg.yaml 20 | USER xray 21 | ENTRYPOINT ["/xray", "-t", "0.0.0.0:2000", "-b", "0.0.0.0:2000"] 22 | EXPOSE 2000/udp 23 | EXPOSE 2000/tcp 24 | -------------------------------------------------------------------------------- /Dockerfile.amazonlinux: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM --platform=$BUILDPLATFORM golang:1.24.2-alpine AS build-env 3 | ARG TARGETPLATFORM 4 | 5 | RUN apk update && apk add ca-certificates 6 | 7 | WORKDIR /workspace 8 | 9 | COPY . . 10 | 11 | RUN adduser -D -u 10001 xray 12 | ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64} 13 | RUN Tool/src/build-in-docker.sh 14 | 15 | FROM amazonlinux:latest 16 | COPY --from=build-env /workspace/xray . 17 | COPY --from=build-env /etc/passwd /etc/passwd 18 | COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 19 | COPY pkg/cfg.yaml /etc/amazon/xray/cfg.yaml 20 | 21 | RUN ln -s /xray /usr/bin/xray 22 | 23 | USER xray 24 | ENTRYPOINT ["/xray", "-t", "0.0.0.0:2000", "-b", "0.0.0.0:2000"] 25 | EXPOSE 2000/udp 26 | EXPOSE 2000/tcp 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS X-Ray Daemon 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/aws/aws-xray-daemon.svg?branch=master)](https://travis-ci.org/aws/aws-xray-daemon) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/aws/aws-xray-daemon)](https://goreportcard.com/report/github.com/aws/aws-xray-daemon) 3 | 4 | # AWS X-Ray Daemon 5 | 6 | The AWS X-Ray daemon is a software application that listens for traffic on UDP port 2000, gathers raw segment data, and relays it to the AWS X-Ray API. 7 | The daemon works in conjunction with the AWS X-Ray SDKs and must be running so that data sent by the SDKs can reach the X-Ray service. For more information, 8 | see [AWS X-Ray Daemon](https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon.html). 9 | 10 | ## Getting Help 11 | 12 | Use the following community resources for getting help with the AWS X-Ray Daemon. We use the GitHub issues for tracking bugs and feature requests. 13 | 14 | * Ask a question in the [AWS X-Ray Forum](https://forums.aws.amazon.com/forum.jspa?forumID=241&start=0). 15 | * Open a support ticket with [AWS Support](http://docs.aws.amazon.com/awssupport/latest/user/getting-started.html). 16 | * If you think you may have found a bug, open an [issue](https://github.com/aws/aws-xray-daemon/issues/new). 17 | * For contributing guidelines refer [CONTRIBUTING.md](https://github.com/aws/aws-xray-daemon/blob/master/CONTRIBUTING.md). 18 | 19 | ## Sending Segment Documents 20 | 21 | The X-Ray SDK sends segment documents to the daemon to avoid making calls to AWS directly. You can send the segment/subsegment in JSON over UDP port 2000 22 | to the X-Ray daemon, prepended by the daemon header : `{"format": "json", "version": 1}\n` 23 | 24 | ``` 25 | {"format": "json", "version": 1}\n{} 26 | ``` 27 | For more details refer : [Link](https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html) 28 | 29 | ## Installing 30 | 31 | The AWS X-Ray Daemon is compatible with Go 1.8 and later. 32 | 33 | Install the daemon using the following command: 34 | 35 | ``` 36 | go get -u github.com/aws/aws-xray-daemon/... 37 | ``` 38 | 39 | ## Credential Configuration 40 | 41 | The AWS X-Ray Daemon follows default credential resolution for the [aws-sdk-go](https://docs.aws.amazon.com/sdk-for-go/api/index.html#hdr-Configuring_Credentials). 42 | 43 | Follow the [guidelines](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html) for the credential configuration. 44 | 45 | ## Daemon Usage (command line args) 46 | 47 | Usage: xray [options] 48 | 49 | | | | Description | 50 | | --- | --- | --- | 51 | | -a | --resource-arn | Amazon Resource Name (ARN) of the AWS resource running the daemon. | 52 | | -o | --local-mode | Don't check for EC2 instance metadata. | 53 | | -m | --buffer-memory | Change the amount of memory in MB that buffers can use (minimum 3). | 54 | | -n | --region | Send segments to X-Ray service in a specific region. | 55 | | -b | --bind | Overrides default UDP address (127.0.0.1:2000). | 56 | | -t | --bind-tcp | Overrides default TCP address (127.0.0.1:2000). | 57 | | -r | --role-arn | Assume the specified IAM role to upload segments to a different account. | 58 | | -c | --config | Load a configuration file from the specified path. | 59 | | -f | --log-file | Output logs to the specified file path. | 60 | | -l | --log-level | Log level, from most verbose to least: dev, debug, info, warn, error, prod (default). | 61 | | -p | --proxy-address | Proxy address through which to upload segments. | 62 | | -v | --version | Show AWS X-Ray daemon version. | 63 | | -h | --help | Show this screen | 64 | 65 | ## Build 66 | 67 | `make build` would build binaries and .zip files in `/build` folder for Linux, MacOS, and Windows platforms. 68 | 69 | ### Linux 70 | 71 | `make build-linux` would build binaries and .zip files in `/build` folder for the Linux platform. 72 | 73 | ### MAC 74 | 75 | `make build-mac` would build binaries and .zip files in `/build` folder for the MacOS platform. 76 | 77 | ### Windows 78 | 79 | `make build-windows` would build binaries and .zip files in `/build` folder for the Windows platform. 80 | 81 | ## Build for ARM achitecture 82 | Currently, the `make build` script builds artifacts for AMD architecture. You can build the X-Ray Daemon for ARM by using the `go build` command and setting the `GOARCH` to `arm64`. To build the daemon binary on a linux ARM machine, you can use the following command: 83 | ``` 84 | GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -o xray cmd/tracing/daemon.go cmd/tracing/tracing.go 85 | ``` 86 | As of Aug 31, 2020, windows and darwin builds for ARM64 are not supported by `go build`. 87 | 88 | ## Pulling X-Ray Daemon image from ECR Public Gallery 89 | Before pulling an image you should authenticate your docker client to the Amazon ECR public registry. For registry authentication options follow this [link](https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html#public-registry-auth) 90 | 91 | Run below command to authenticate to public ECR registry using `get-login-password` (AWS CLI) 92 | 93 | `` 94 | aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws 95 | `` 96 | 97 | #### Pull alpha tag from Public ECR Gallery 98 | `` 99 | docker pull public.ecr.aws/xray/aws-xray-daemon:alpha 100 | `` 101 | 102 | #### Pull released version tag from Public ECR Gallery 103 | `` 104 | docker pull public.ecr.aws/xray/aws-xray-daemon:3.2.0 105 | `` 106 | 107 | NOTE: We are not recommending to use daemon image with alpha tag in production environment. For production environment customer should pull in an image with released tag. 108 | 109 | ## X-Ray Daemon Performance Report 110 | 111 | **EC2 Instance Type:** T2.Micro [1 vCPU, 1 GB Memory] 112 | 113 | **Collection time:** 10 minutes per TPS (TPS = Number of segments sent to daemon in 1 second) 114 | 115 | **Daemon version tested:** 3.3.6 116 | 117 | | **TPS** | **Avg CPU Usage (%)** | **Avg Memory Usage (MB)** | 118 | |---------|-----------------------|---------------------------| 119 | | 0 | 0 | 17.07 | 120 | | 100 | 0.9 | 28.5 | 121 | | 200 | 1.87 | 29.3 | 122 | | 400 | 3.76 | 29.1 | 123 | | 1000 | 9.36 | 29.5 | 124 | | 2000 | 18.9 | 29.7 | 125 | | 4000 | 38.3 | 29.5 | 126 | 127 | 128 | ## Testing 129 | 130 | `make test` will run unit tests for the X-Ray daemon. 131 | 132 | ## License 133 | 134 | This library is licensed under the Apache 2.0 License. 135 | -------------------------------------------------------------------------------- /Tool/src/build-in-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BUILD_FOLDER=${TARGETPLATFORM/\//-} 4 | 5 | echo "Building for platform ${TARGETPLATFORM}" 6 | 7 | if [[ -d "/workspace/build/xray-${BUILD_FOLDER}" ]]; then 8 | echo "Copying prebuilt binary" 9 | cp /workspace/build/xray-${BUILD_FOLDER}/xray /workspace/ 10 | else 11 | echo "Building from source" 12 | CGO_ENABLED=0 GOOS=linux GOARCH=$(echo $TARGETPLATFORM | cut -d'/' -f2) go build -a -ldflags '-extldflags "-static"' -o /workspace/xray /workspace/cmd/tracing 13 | fi 14 | -------------------------------------------------------------------------------- /Tool/src/packaging/build-package-legacy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo "****************************************" 6 | echo "Creating legacy artifacts for 3.x with older names" 7 | echo "****************************************" 8 | 9 | cd ${BGO_SPACE}/build/dist 10 | 11 | echo "Building and packaging legacy artifacts for Linux" 12 | cp ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-amd64-${VERSION}.zip ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-3.x.zip 13 | cp ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-amd64-${VERSION}.rpm ${BGO_SPACE}/build/dist/aws-xray-daemon-3.x.rpm 14 | cp ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-amd64-${VERSION}.deb ${BGO_SPACE}/build/dist/aws-xray-daemon-3.x.deb 15 | cp ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-arm64-${VERSION}.rpm ${BGO_SPACE}/build/dist/aws-xray-daemon-arm64-3.x.rpm 16 | cp ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-arm64-${VERSION}.deb ${BGO_SPACE}/build/dist/aws-xray-daemon-arm64-3.x.deb 17 | 18 | echo "Building and packaging legacy artifacts for MacOS" 19 | unzip -q -o aws-xray-daemon-macos-amd64-${VERSION}.zip 20 | mv xray xray_mac 21 | zip aws-xray-daemon-macos-3.x.zip xray_mac cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 22 | rm xray_mac 23 | 24 | echo "Building and packaging legacy artifacts for Windows" 25 | unzip -q -o aws-xray-daemon-windows-amd64-service-${VERSION}.zip 26 | mv xray_service.exe xray.exe 27 | zip aws-xray-daemon-windows-service-3.x.zip xray.exe cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 28 | rm xray.exe 29 | 30 | unzip -q -o aws-xray-daemon-windows-amd64-${VERSION}.zip 31 | mv xray.exe xray_windows.exe 32 | zip aws-xray-daemon-windows-process-3.x.zip xray_windows.exe cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 33 | rm xray_windows.exe 34 | 35 | rm cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 36 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/build_deb_linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ARCH=$1 4 | 5 | echo "****************************************" 6 | echo "Creating deb file for Ubuntu Linux ${ARCH}" 7 | echo "****************************************" 8 | 9 | echo "Creating debian folders" 10 | 11 | mkdir -p ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/bin/ 12 | mkdir -p ${BGO_SPACE}/bin/debian_${ARCH}/debian/etc/init/ 13 | mkdir -p ${BGO_SPACE}/bin/debian_${ARCH}/debian/etc/amazon/xray/ 14 | mkdir -p ${BGO_SPACE}/bin/debian_${ARCH}/debian/var/lib/amazon/xray/ 15 | mkdir -p ${BGO_SPACE}/bin/debian_${ARCH}/debian/lib/systemd/system/ 16 | mkdir -p ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/lintian/overrides/ 17 | mkdir -p ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/ 18 | 19 | echo "Copying application files" 20 | 21 | cp ${BGO_SPACE}/build/xray-linux-${ARCH}/xray ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/bin/ 22 | cp ${BGO_SPACE}/build/dist/cfg.yaml ${BGO_SPACE}/bin/debian_${ARCH}/debian/etc/amazon/xray/cfg.yaml 23 | cp ${BGO_SPACE}/Tool/src/packaging/debian/xray.conf ${BGO_SPACE}/bin/debian_${ARCH}/debian/etc/init/xray.conf 24 | cp ${BGO_SPACE}/Tool/src/packaging/debian/xray.service ${BGO_SPACE}/bin/debian_${ARCH}/debian/lib/systemd/system/xray.service 25 | 26 | echo "Copying debian package config files" 27 | 28 | cp ${BGO_SPACE}/Tool/src/packaging/debian/conffiles ${BGO_SPACE}/bin/debian_${ARCH}/debian/ 29 | cp ${BGO_SPACE}/Tool/src/packaging/debian/preinst ${BGO_SPACE}/bin/debian_${ARCH}/debian/ 30 | cp ${BGO_SPACE}/Tool/src/packaging/debian/postinst ${BGO_SPACE}/bin/debian_${ARCH}/debian/ 31 | cp ${BGO_SPACE}/Tool/src/packaging/debian/prerm ${BGO_SPACE}/bin/debian_${ARCH}/debian/ 32 | cp ${BGO_SPACE}/Tool/src/packaging/debian/lintian-overrides ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/lintian/overrides/xray 33 | 34 | echo "Constructing the control file" 35 | 36 | echo "Package: xray" > ${BGO_SPACE}/bin/debian_${ARCH}/debian/control 37 | echo "Architecture: ${ARCH}" >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/control 38 | echo -n "Version: " >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/control 39 | echo $VERSION >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/control 40 | cat ${BGO_SPACE}/Tool/src/packaging/debian/control >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/control 41 | 42 | echo "Constructing the copyright file" 43 | cat ${BGO_SPACE}/LICENSE >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/copyright 44 | echo "\n ======================== \n" >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/copyright 45 | cat ${BGO_SPACE}/THIRD-PARTY-LICENSES.txt >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/copyright 46 | 47 | echo "Constructing the changelog file" 48 | 49 | echo -n "xray (" > ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/changelog 50 | echo $VERSION >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/changelog 51 | echo "-1) precise-proposed; urgency=low" >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/changelog 52 | cat ${BGO_SPACE}/Tool/src/packaging/debian/changelog >> ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/changelog 53 | 54 | cp ${BGO_SPACE}/Tool/src/packaging/debian/changelog.Debian ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/ 55 | cp ${BGO_SPACE}/Tool/src/packaging/debian/debian-binary ${BGO_SPACE}/bin/debian_${ARCH}/debian/ 56 | 57 | echo "Setting permissioning as required by debian" 58 | 59 | cd ${BGO_SPACE}/bin/debian_${ARCH}/; find ./debian -type d | xargs chmod 755; cd ~- 60 | 61 | echo "Compressing changelog" 62 | 63 | cd ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/; export GZIP=-9; tar cvzf changelog.gz changelog --owner=0 --group=0 ; cd ~- 64 | cd ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/; export GZIP=-9; tar cvzf changelog.Debian.gz changelog.Debian --owner=0 --group=0; cd ~- 65 | 66 | rm ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/changelog 67 | rm ${BGO_SPACE}/bin/debian_${ARCH}/debian/usr/share/doc/xray/changelog.Debian 68 | 69 | echo "Creating tar" 70 | # the below permission is required by debian 71 | cd ${BGO_SPACE}/bin/debian_${ARCH}/debian/; tar czf data.tar.gz usr etc lib --owner=0 --group=0 ; cd ~- 72 | cd ${BGO_SPACE}/bin/debian_${ARCH}/debian/; tar czf control.tar.gz control conffiles preinst postinst prerm copyright --owner=0 --group=0 ; cd ~- 73 | 74 | echo "Constructing the deb package" 75 | ar r ${BGO_SPACE}/bin/xray.deb ${BGO_SPACE}/bin/debian_${ARCH}/debian/debian-binary 76 | ar r ${BGO_SPACE}/bin/xray.deb ${BGO_SPACE}/bin/debian_${ARCH}/debian/control.tar.gz 77 | ar r ${BGO_SPACE}/bin/xray.deb ${BGO_SPACE}/bin/debian_${ARCH}/debian/data.tar.gz 78 | cp ${BGO_SPACE}/bin/xray.deb ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-${ARCH}-${VERSION}.deb 79 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/changelog: -------------------------------------------------------------------------------- 1 | 2 | * Initial release 3 | 4 | -- Amazon.com, Inc. Fri, 05 Nov 2016 14:00:00 +1000 5 | 6 | 7 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/changelog.Debian: -------------------------------------------------------------------------------- 1 | Old Changelog: 2 | Amazon X-Ray Debian maintainer and upstream author are identical. 3 | Therefore see also normal changelog file for Debian changes. 4 | 5 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/conffiles: -------------------------------------------------------------------------------- 1 | /etc/init/xray.conf 2 | /etc/amazon/xray/cfg.yaml 3 | /lib/systemd/system/xray.service 4 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/control: -------------------------------------------------------------------------------- 1 | Section: admin 2 | Depends: libc6 3 | Priority: optional 4 | Copyright: Apache License, Version 2.0 5 | Suggests: aws-xray-doc 6 | Maintainer: Amazon.com, Inc. 7 | Description: Amazon X-Ray is a platform makes it easy for customers to analyze the behavior of distributed applications. 8 | Please visit the AWS Developer Tools page for additional information. 9 | 10 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/debian-binary: -------------------------------------------------------------------------------- 1 | 2.0 2 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/lintian-overrides: -------------------------------------------------------------------------------- 1 | # The Go compiler is currently unable to produce read-only relocations 2 | # (it produces static binaries). 3 | hardening-no-relro /usr/bin/xray 4 | 5 | # Amazon Xray Debian maintainer and upstream author are identical. 6 | # Therefore see normal changelog file for Debian changes. 7 | syntax-error-in-debian-changelog 8 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/postinst: -------------------------------------------------------------------------------- 1 | echo "Starting xray daemon" 2 | initSystem=$(cat /proc/1/comm) 3 | if [ $initSystem = init ] 4 | then 5 | start xray || true 6 | elif [ $initSystem = systemd ] 7 | then 8 | systemctl start xray 9 | systemctl daemon-reload 10 | fi -------------------------------------------------------------------------------- /Tool/src/packaging/debian/preinst: -------------------------------------------------------------------------------- 1 | echo "Preparing for install" 2 | initSystem=$(cat /proc/1/comm) 3 | useradd --system -s /bin/false xray 4 | mkdir -m 755 /var/log/xray/ 5 | chown xray /var/log/xray/ 6 | if [ $initSystem = init ] 7 | then 8 | stop xray || true 9 | elif [ $initSystem = systemd ] 10 | then 11 | systemctl stop xray 12 | systemctl daemon-reload 13 | fi 14 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/prerm: -------------------------------------------------------------------------------- 1 | echo "Stopping xray daemon" 2 | initSystem=$(cat /proc/1/comm) 3 | if [ $initSystem = init ] 4 | then 5 | stop xray || true 6 | elif [ $initSystem = systemd ] 7 | then 8 | systemctl stop xray 9 | systemctl daemon-reload 10 | fi -------------------------------------------------------------------------------- /Tool/src/packaging/debian/xray.conf: -------------------------------------------------------------------------------- 1 | # Location /etc/init/xray.conf 2 | # initctl start xray 3 | start on runlevel [2345] 4 | stop on runlevel [!2345] 5 | 6 | respawn 7 | exec su -s /bin/bash -c "xray -f /var/log/xray/xray.log" xray 8 | -------------------------------------------------------------------------------- /Tool/src/packaging/debian/xray.service: -------------------------------------------------------------------------------- 1 | # Location: /etc/systemd/system/xray.service 2 | # systemctl enable xray 3 | # systemctl start xray 4 | # systemctl | grep xray 5 | # https://www.freedesktop.org/software/systemd/man/systemd.unit.html 6 | 7 | [Unit] 8 | Description=AWS X-Ray Daemon 9 | After=network-online.target 10 | 11 | [Service] 12 | Type=exec 13 | WorkingDirectory=/usr/bin/ 14 | User=xray 15 | Group=xray 16 | ExecStart=/usr/bin/xray -f /var/log/xray/xray.log 17 | Restart=always 18 | LogsDirectory=xray 19 | LogsDirectoryMode=0755 20 | ConfigurationDirectory=amazon/xray 21 | ConfigurationDirectoryMode=0755 22 | 23 | [Install] 24 | WantedBy=network-online.target 25 | -------------------------------------------------------------------------------- /Tool/src/packaging/linux/README.md: -------------------------------------------------------------------------------- 1 | sudo yum install rpmdevtools 2 | -------------------------------------------------------------------------------- /Tool/src/packaging/linux/build_rpm_linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | ARCH=$1 6 | if [[ $ARCH == "amd64" ]] 7 | then 8 | BUILD_ARCH=x86_64 9 | elif [[ $ARCH == "arm64" ]] 10 | then 11 | BUILD_ARCH=aarch64 12 | else 13 | echo "Invalid architecture input. Exiting" 14 | exit 1 15 | fi 16 | 17 | echo "*************************************************" 18 | echo "Creating rpm file for Amazon Linux and RHEL ${ARCH}" 19 | echo "*************************************************" 20 | 21 | rm -rf ${BGO_SPACE}/bin/linux_${ARCH}/linux 22 | 23 | echo "Creating rpmbuild workspace" 24 | mkdir -p ${BGO_SPACE}/bin/linux_${ARCH}/linux/rpmbuild/{RPMS,SRPMS,BUILD,COORD_SOURCES,SPECS,DATA_SOURCES} 25 | mkdir -p ${BGO_SPACE}/bin/linux_${ARCH}/linux/usr/bin/ 26 | mkdir -p ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/amazon/xray/ 27 | mkdir -p ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/init/ 28 | mkdir -p ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/systemd/system/ 29 | 30 | echo "Copying application files" 31 | cp ${BGO_SPACE}/build/xray-linux-${ARCH}/xray ${BGO_SPACE}/bin/linux_${ARCH}/linux/usr/bin/ 32 | cp ${BGO_SPACE}/pkg/cfg.yaml ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/amazon/xray/cfg.yaml 33 | cp ${BGO_SPACE}/Tool/src/packaging/linux/xray.conf ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/init/ 34 | cp ${BGO_SPACE}/Tool/src/packaging/linux/xray.service ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/systemd/system/ 35 | cp ${BGO_SPACE}/LICENSE ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/amazon/xray/ 36 | cp ${BGO_SPACE}/THIRD-PARTY-LICENSES.txt ${BGO_SPACE}/bin/linux_${ARCH}/linux/etc/amazon/xray/ 37 | 38 | echo "Creating the rpm package" 39 | SPEC_FILE="${BGO_SPACE}/Tool/src/packaging/linux/xray.spec" 40 | BUILD_ROOT="${BGO_SPACE}/bin/linux_${ARCH}/linux" 41 | rpmbuild --target ${BUILD_ARCH}-linux --define "rpmver ${VERSION}" --define "_topdir bin/linux_${ARCH}/linux/rpmbuild" -bb --buildroot ${BUILD_ROOT} ${SPEC_FILE} 42 | 43 | echo "Copying rpm files to bin" 44 | cp ${BGO_SPACE}/bin/linux_${ARCH}/linux/rpmbuild/RPMS/${BUILD_ARCH}/*.rpm ${BGO_SPACE}/build/dist/aws-xray-daemon-linux-${ARCH}-${VERSION}.rpm 45 | -------------------------------------------------------------------------------- /Tool/src/packaging/linux/build_zip_linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "****************************************" 3 | echo "Creating zip file for Linux amd64 and arm64" 4 | echo "****************************************" 5 | 6 | DIST_FOLDER=${BGO_SPACE}/build/dist/ 7 | cd $DIST_FOLDER 8 | 9 | cp ../xray-linux-amd64/xray xray 10 | zip aws-xray-daemon-linux-amd64-${VERSION}.zip xray cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 11 | rm xray 12 | 13 | cp ../xray-linux-arm64/xray xray 14 | zip aws-xray-daemon-linux-arm64-${VERSION}.zip xray cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 15 | rm xray -------------------------------------------------------------------------------- /Tool/src/packaging/linux/xray.conf: -------------------------------------------------------------------------------- 1 | # Location /etc/init/xray.conf 2 | # initctl start xray 3 | start on (runlevel [345] and started network) 4 | stop on (runlevel [!345] or stopping network) 5 | 6 | respawn 7 | 8 | script 9 | echo $$ > /var/run/xray.pid 10 | exec su -s /bin/bash -c "xray -f /var/log/xray/xray.log" xray 11 | end script 12 | 13 | -------------------------------------------------------------------------------- /Tool/src/packaging/linux/xray.service: -------------------------------------------------------------------------------- 1 | # Location: /etc/systemd/system/xray.service 2 | # systemctl enable xray 3 | # systemctl start xray 4 | # systemctl | grep xray 5 | # https://www.freedesktop.org/software/systemd/man/systemd.unit.html 6 | 7 | [Unit] 8 | Description=AWS X-Ray Daemon 9 | After=network-online.target 10 | 11 | [Service] 12 | Type=exec 13 | WorkingDirectory=/usr/bin/ 14 | User=xray 15 | Group=xray 16 | ExecStart=/usr/bin/xray -f /var/log/xray/xray.log 17 | Restart=always 18 | LogsDirectory=xray 19 | LogsDirectoryMode=0755 20 | ConfigurationDirectory=amazon/xray 21 | ConfigurationDirectoryMode=0755 22 | 23 | [Install] 24 | WantedBy=network-online.target 25 | -------------------------------------------------------------------------------- /Tool/src/packaging/linux/xray.spec: -------------------------------------------------------------------------------- 1 | Name: xray 2 | Version: %rpmver 3 | Release: 1 4 | Summary: X-Ray daemon 5 | 6 | Group: Amazon/Tools 7 | License: Apache License, Version 2.0 8 | URL: http://docs.aws.amazon.com/ 9 | 10 | %description 11 | This package provides daemon to send trace segments to xray dataplane 12 | 13 | %files 14 | /usr/bin/xray 15 | 16 | %%license /etc/amazon/xray/LICENSE 17 | %%license /etc/amazon/xray/THIRD-PARTY-LICENSES.txt 18 | 19 | %config(noreplace) /etc/amazon/xray/cfg.yaml 20 | %config(noreplace) /etc/init/xray.conf 21 | %config(noreplace) /etc/systemd/system/xray.service 22 | 23 | %pre 24 | # First time install create user and folders 25 | if [ $1 -eq 1 ]; then 26 | useradd --system -s /bin/false xray 27 | mkdir -m 755 /var/log/xray/ 28 | chown xray /var/log/xray/ 29 | else 30 | # Stop the agent before the upgrade 31 | if [ $1 -ge 2 ]; then 32 | if [[ `/sbin/init --version` =~ upstart ]]; then 33 | /sbin/stop xray 34 | elif [[ `systemctl` =~ -\.mount ]]; then 35 | systemctl stop xray 36 | systemctl daemon-reload 37 | fi 38 | fi 39 | fi 40 | 41 | %preun 42 | # Stop the agent after uninstall 43 | if [ $1 -eq 0 ] ; then 44 | if [[ `/sbin/init --version` =~ upstart ]]; then 45 | /sbin/stop xray 46 | sleep 1 47 | elif [[ `systemctl` =~ -\.mount ]]; then 48 | systemctl stop xray 49 | systemctl daemon-reload 50 | fi 51 | fi 52 | 53 | %posttrans 54 | # Start the agent after initial install or upgrade 55 | if [ $1 -ge 0 ]; then 56 | if [[ `/sbin/init --version` =~ upstart ]]; then 57 | /sbin/start xray 58 | elif [[ `systemctl` =~ -\.mount ]]; then 59 | systemctl start xray 60 | systemctl daemon-reload 61 | fi 62 | fi 63 | 64 | %clean 65 | # rpmbuild deletes $buildroot after building, specifying clean section to make 66 | # sure it is not deleted 67 | 68 | -------------------------------------------------------------------------------- /Tool/src/packaging/major-version-packages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | VERSION=$1 6 | 7 | echo "****************************************" 8 | echo "Creating major version packages for VERSION=${VERSION}" 9 | echo "****************************************" 10 | 11 | # Git SHA-1 hash for commits is 40 char long 12 | if [[ ${#VERSION} -eq 40 ]] 13 | then 14 | echo "Since the version is a commit hash, the major version will be \"latest\"." 15 | MAJOR_VERSION=latest 16 | else 17 | IFS='.' read -ra PARTS <<< "$VERSION" 18 | MAJOR_VERSION=${PARTS[0]}.x 19 | echo "This is a release version. Will create binaries with \"$MAJOR_VERSION\" suffix." 20 | fi 21 | 22 | cd ${BGO_SPACE}/build/dist 23 | 24 | for filename in *; do 25 | newFilename="${filename/$VERSION/$MAJOR_VERSION}" 26 | if [[ $newFilename != $filename ]] 27 | then 28 | cp $filename $newFilename 29 | fi 30 | done -------------------------------------------------------------------------------- /Tool/src/packaging/osx/build_zip_osx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "****************************************" 3 | echo "Creating zip file for OS-X amd64" 4 | echo "****************************************" 5 | 6 | DIST_FOLDER=${BGO_SPACE}/build/dist/ 7 | cd $DIST_FOLDER 8 | 9 | cp ../xray-mac-amd64/xray xray 10 | zip aws-xray-daemon-macos-amd64-${VERSION}.zip xray cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 11 | rm xray 12 | 13 | cp ../xray-mac-arm64/xray xray 14 | zip aws-xray-daemon-macos-arm64-${VERSION}.zip xray cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 15 | rm xray 16 | -------------------------------------------------------------------------------- /Tool/src/packaging/sign-packages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "****************************************" 4 | echo "GPG signing for daemon assets" 5 | echo "****************************************" 6 | 7 | echo "Starting gpg signing for zip files" 8 | for filename in build/dist/*; do 9 | ext="${filename##*.}" 10 | if [ $ext == "zip" ]; then 11 | gpg --output $filename.sig --detach-sig $filename 12 | fi 13 | done 14 | echo "Finished gpg signing for zip files" 15 | 16 | 17 | echo "Starting GPG signing for rpm files" 18 | rpm -qa gpg-pubkey 19 | 20 | echo "Create rpmmacros file" 21 | rm ~/.rpmmacros 22 | echo -e "%_signature gpg\n%_gpg_path ~/.gnupg\n%_gpg_name AWS X-Ray\n%_gpgbin /usr/bin/gpg" >> ~/.rpmmacros 23 | cat ~/.rpmmacros 24 | 25 | for filename in build/dist/*; do 26 | ext="${filename##*.}" 27 | if [ $ext == "rpm" ]; then 28 | rpmsign --addsign $filename 29 | fi 30 | done 31 | echo "Finished GPG signing for rpm files" 32 | 33 | echo "Starting GPG signing for deb files" 34 | for filename in build/dist/*; do 35 | ext="${filename##*.}" 36 | if [ $ext == "deb" ]; then 37 | ar x $filename 38 | cat debian-binary control.tar.gz data.tar.gz > /tmp/combined-contents 39 | gpg -abs -o _gpgorigin /tmp/combined-contents 40 | ar rc $filename _gpgorigin debian-binary control.tar.gz data.tar.gz 41 | rm /tmp/combined-contents 42 | rm control.tar.gz 43 | rm data.tar.gz 44 | rm debian-binary 45 | rm _gpgorigin 46 | fi 47 | done 48 | echo "Finished GPG signing for deb files" 49 | -------------------------------------------------------------------------------- /Tool/src/packaging/windows/build_zip_win.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "****************************************" 3 | echo "Creating zip file for Windows amd64" 4 | echo "****************************************" 5 | 6 | DIST_FOLDER=${BGO_SPACE}/build/dist/ 7 | cd $DIST_FOLDER 8 | 9 | cp ../xray-windows-amd64/xray_service.exe xray_service.exe 10 | zip aws-xray-daemon-windows-amd64-service-${VERSION}.zip xray_service.exe cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 11 | rm xray_service.exe 12 | 13 | cp ../xray-windows-amd64/xray.exe xray.exe 14 | zip aws-xray-daemon-windows-amd64-${VERSION}.zip xray.exe cfg.yaml LICENSE THIRD-PARTY-LICENSES.txt 15 | rm xray.exe 16 | -------------------------------------------------------------------------------- /cmd/tracing/tracing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | // +build !windows 11 | 12 | package main 13 | 14 | import ( 15 | "os" 16 | "os/signal" 17 | "syscall" 18 | "time" 19 | 20 | log "github.com/cihub/seelog" 21 | ) 22 | 23 | func (d *Daemon) blockSignalReceived() { 24 | sigs := make(chan os.Signal, 1) 25 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, os.Kill) 26 | s := <-sigs 27 | log.Debugf("Shutdown Initiated. Current epoch in nanoseconds: %v", time.Now().UnixNano()) 28 | log.Infof("Got shutdown signal: %v", s) 29 | d.stop() 30 | } 31 | 32 | func main() { 33 | d := initDaemon(config) 34 | defer d.close() 35 | go func() { 36 | d.blockSignalReceived() 37 | }() 38 | runDaemon(d) 39 | } 40 | -------------------------------------------------------------------------------- /cmd/tracing/tracing_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | // +build windows 11 | 12 | package main 13 | 14 | import ( 15 | "time" 16 | 17 | "golang.org/x/sys/windows/svc" 18 | ) 19 | 20 | const serviceName = "AmazonX-RayDaemon" 21 | 22 | func main() { 23 | svc.Run(serviceName, &TracingDaemonService{}) 24 | } 25 | 26 | // Structure for X-Ray daemon as a service. 27 | type TracingDaemonService struct{} 28 | 29 | // Execute xray as Windows service. Implement golang.org/x/sys/windows/svc#Handler. 30 | func (a *TracingDaemonService) Execute(args []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { 31 | 32 | // notify service controller status is now StartPending 33 | s <- svc.Status{State: svc.StartPending} 34 | 35 | // start service 36 | d := initDaemon(config) 37 | // Start a routine to monitor all channels/routines initiated are closed 38 | // This is required for windows as windows daemon wait for process to finish using infinite for loop below 39 | go d.close() 40 | runDaemon(d) 41 | // update service status to Running 42 | const acceptCmds = svc.AcceptStop | svc.AcceptShutdown 43 | s <- svc.Status{State: svc.Running, Accepts: acceptCmds} 44 | loop: 45 | // using an infinite loop to wait for ChangeRequests 46 | for { 47 | // block and wait for ChangeRequests 48 | c := <-r 49 | 50 | // handle ChangeRequest, svc.Pause is not supported 51 | switch c.Cmd { 52 | case svc.Interrogate: 53 | s <- c.CurrentStatus 54 | // Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4 55 | time.Sleep(100 * time.Millisecond) 56 | s <- c.CurrentStatus 57 | case svc.Stop, svc.Shutdown: 58 | break loop 59 | default: 60 | continue loop 61 | } 62 | } 63 | s <- svc.Status{State: svc.StopPending} 64 | d.stop() 65 | return false, 0 66 | } 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws/aws-xray-daemon 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/aws/aws-sdk-go v1.44.298 9 | github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 10 | github.com/shirou/gopsutil v2.19.10+incompatible 11 | github.com/stretchr/testify v1.4.0 12 | golang.org/x/net v0.38.0 13 | golang.org/x/sys v0.31.0 14 | gopkg.in/yaml.v2 v2.2.8 15 | ) 16 | 17 | require ( 18 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 19 | github.com/davecgh/go-spew v1.1.0 // indirect 20 | github.com/go-ole/go-ole v1.2.4 // indirect 21 | github.com/jmespath/go-jmespath v0.4.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/stretchr/objx v0.1.0 // indirect 24 | golang.org/x/text v0.23.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= 2 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 3 | github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= 4 | github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= 5 | github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= 6 | github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= 10 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 11 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 12 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 13 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 14 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 15 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/shirou/gopsutil v2.19.10+incompatible h1:lA4Pi29JEVIQIgATSeftHSY0rMGI9CLrl2ZvDLiahto= 19 | github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 20 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 23 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 24 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 25 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 26 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 27 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 28 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 29 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 30 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 31 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 32 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 33 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 34 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 36 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 43 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 44 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 45 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 46 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 49 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 50 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 51 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 52 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 53 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 54 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 55 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 56 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 57 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 60 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 61 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 62 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | PREFIX :=. 2 | 3 | # Initialize workspace if it's empty 4 | ifeq ($(WORKSPACE),) 5 | WORKSPACE := $(shell pwd)/../../../../ 6 | endif 7 | 8 | export GO_LDFLAGS=-ldflags "-s -w -X github.com/aws/aws-xray-daemon/pkg/cfg.Version=${VERSION}" 9 | 10 | # Initialize BGO_SPACE 11 | export BGO_SPACE=$(shell pwd) 12 | path := $(BGO_SPACE):$(WORKSPACE) 13 | 14 | build: create-folder copy-file build-mac-amd64 build-mac-arm64 build-linux-amd64 build-linux-arm64 build-windows 15 | 16 | packaging: zip-linux zip-osx zip-win package-rpm package-deb major-version-packages build-package-legacy 17 | 18 | release: build test packaging clean-folder 19 | 20 | .PHONY: create-folder 21 | create-folder: 22 | mkdir -p build/dist 23 | 24 | .PHONY: copy-file 25 | copy-file: 26 | cp pkg/cfg.yaml build/dist/ 27 | cp $(BGO_SPACE)/LICENSE build/dist 28 | cp $(BGO_SPACE)/THIRD-PARTY-LICENSES.txt build/dist 29 | 30 | .PHONY: build-mac-amd64 31 | build-mac-amd64: 32 | @echo "Build for MAC amd64" 33 | GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(GO_LDFLAGS) -o $(BGO_SPACE)/build/xray-mac-amd64/xray ${PREFIX}/cmd/tracing/daemon.go ${PREFIX}/cmd/tracing/tracing.go 34 | 35 | .PHONY: build-mac-arm64 36 | build-mac-arm64: 37 | @echo "Build for MAC arm64" 38 | GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build $(GO_LDFLAGS) -o $(BGO_SPACE)/build/xray-mac-arm64/xray ${PREFIX}/cmd/tracing/daemon.go ${PREFIX}/cmd/tracing/tracing.go 39 | 40 | .PHONY: build-linux-amd64 41 | build-linux-amd64: 42 | @echo "Build for Linux amd64" 43 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(GO_LDFLAGS) -o $(BGO_SPACE)/build/xray-linux-amd64/xray ${PREFIX}/cmd/tracing/daemon.go ${PREFIX}/cmd/tracing/tracing.go 44 | 45 | .PHONY: build-linux-arm64 46 | build-linux-arm64: 47 | @echo "Build for Linux arm64" 48 | GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build $(GO_LDFLAGS) -o $(BGO_SPACE)/build/xray-linux-arm64/xray ${PREFIX}/cmd/tracing/daemon.go ${PREFIX}/cmd/tracing/tracing.go 49 | 50 | .PHONY: build-windows 51 | build-windows: 52 | @echo "Build for Windows amd64" 53 | GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(GO_LDFLAGS) -o $(BGO_SPACE)/build/xray-windows-amd64/xray_service.exe ${PREFIX}/cmd/tracing/daemon.go ${PREFIX}/cmd/tracing/tracing_windows.go 54 | GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(GO_LDFLAGS) -o $(BGO_SPACE)/build/xray-windows-amd64/xray.exe ${PREFIX}/cmd/tracing/daemon.go ${PREFIX}/cmd/tracing/tracing.go 55 | 56 | .PHONY: build-docker 57 | build-docker: 58 | docker build -t amazon/aws-xray-daemon:$VERSION . 59 | 60 | .PHONY: push-docker 61 | push-docker: 62 | docker push amazon/aws-xray-daemon:$VERSION 63 | 64 | .PHONY: zip-linux 65 | zip-linux: 66 | $(BGO_SPACE)/Tool/src/packaging/linux/build_zip_linux.sh 67 | 68 | .PHONY: zip-osx 69 | zip-osx: 70 | $(BGO_SPACE)/Tool/src/packaging/osx/build_zip_osx.sh 71 | 72 | .PHONY: zip-win 73 | zip-win: 74 | $(BGO_SPACE)/Tool/src/packaging/windows/build_zip_win.sh 75 | 76 | .PHONY: package-deb 77 | package-deb: 78 | $(BGO_SPACE)/Tool/src/packaging/debian/build_deb_linux.sh amd64 79 | $(BGO_SPACE)/Tool/src/packaging/debian/build_deb_linux.sh arm64 80 | 81 | .PHONY: package-rpm 82 | package-rpm: 83 | $(BGO_SPACE)/Tool/src/packaging/linux/build_rpm_linux.sh amd64 84 | $(BGO_SPACE)/Tool/src/packaging/linux/build_rpm_linux.sh arm64 85 | 86 | .PHONY: major-version-packages 87 | major-version-packages: 88 | $(BGO_SPACE)/Tool/src/packaging/major-version-packages.sh ${VERSION} 89 | 90 | # This will be removed in the next major version release 91 | .PHONY: build-package-legacy 92 | build-package-legacy: 93 | $(BGO_SPACE)/Tool/src/packaging/build-package-legacy.sh 94 | 95 | .PHONY: test 96 | test: 97 | @echo "Testing daemon" 98 | go test -cover ./... 99 | 100 | vet: 101 | go vet ./... 102 | 103 | lint: 104 | golint ${SDK_BASE_FOLDERS}... 105 | 106 | fmt: 107 | go fmt pkg/... 108 | 109 | .PHONY: clean-folder 110 | clean-folder: 111 | cd build && \ 112 | find . ! -name "xray" ! -name "." -type d -exec rm -rf {} + || true 113 | -------------------------------------------------------------------------------- /pkg/bufferpool/bufferpool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package bufferpool 11 | 12 | import ( 13 | "errors" 14 | "math" 15 | "sync" 16 | ) 17 | 18 | // BufferPool is a structure for storing trace segments. 19 | type BufferPool struct { 20 | // Slice of byte slices to store trace segments. 21 | Buffers []*[]byte 22 | lock sync.Mutex 23 | 24 | // Map to track available buffers in the pool. 25 | bufferHeadHash map[*byte]bool 26 | } 27 | 28 | // Init initializes new BufferPool with bufferLimit buffers, each of bufferSize. 29 | func Init(bufferLimit int, bufferSize int) *BufferPool { 30 | bufferHeadHash := make(map[*byte]bool) 31 | bufferArray := make([]*[]byte, bufferLimit) 32 | for i := 0; i < bufferLimit; i++ { 33 | buf := make([]byte, bufferSize) 34 | bufferArray[i] = &buf 35 | bufferHeadHash[getBufferPointer(&buf)] = true 36 | } 37 | bufferPool := BufferPool{ 38 | Buffers: bufferArray, 39 | lock: sync.Mutex{}, 40 | bufferHeadHash: bufferHeadHash, 41 | } 42 | return &bufferPool 43 | } 44 | 45 | // Get returns available buffer of BufferPool b, nil if not any. 46 | func (b *BufferPool) Get() *[]byte { 47 | b.lock.Lock() 48 | buffers := b.Buffers 49 | buffersLen := len(buffers) 50 | var buf *[]byte 51 | if buffersLen > 0 { 52 | buf = buffers[buffersLen-1] 53 | b.Buffers = buffers[:buffersLen-1] 54 | delete(b.bufferHeadHash, getBufferPointer(buf)) 55 | } 56 | b.lock.Unlock() 57 | return buf 58 | } 59 | 60 | // Return adds buffer buf to BufferPool b. 61 | func (b *BufferPool) Return(buf *[]byte) { 62 | b.lock.Lock() 63 | // Rejecting buffer if already in pool 64 | if b.isBufferAlreadyInPool(buf) { 65 | b.lock.Unlock() 66 | return 67 | } 68 | buffers := b.Buffers 69 | buffersCap := cap(buffers) 70 | buffersLen := len(buffers) 71 | if buffersLen < buffersCap { 72 | buffers = append(buffers, buf) 73 | b.Buffers = buffers 74 | b.bufferHeadHash[getBufferPointer(buf)] = true 75 | } 76 | b.lock.Unlock() 77 | } 78 | 79 | // CurrentBuffersLen returns length of buffers. 80 | func (b *BufferPool) CurrentBuffersLen() int { 81 | b.lock.Lock() 82 | len := len(b.Buffers) 83 | b.lock.Unlock() 84 | return len 85 | } 86 | 87 | func getBufferPointer(buf *[]byte) *byte { 88 | bufVal := *buf 89 | // Using first element as pointer to the whole array as Go array is continuous array 90 | // This might fail if someone return slice of original buffer that was fetched 91 | return &bufVal[0] 92 | } 93 | 94 | func (b *BufferPool) isBufferAlreadyInPool(buf *[]byte) bool { 95 | bufPointer := getBufferPointer(buf) 96 | _, ok := b.bufferHeadHash[bufPointer] 97 | return ok 98 | } 99 | 100 | // GetPoolBufferCount returns number of buffers that can fit in the given buffer pool limit 101 | // where each buffer is of size receiveBufferSize. 102 | func GetPoolBufferCount(bufferPoolLimitMB int, receiveBufferSize int) (int, error) { 103 | if receiveBufferSize <= 0 { 104 | return 0, errors.New("receive buffer size cannot be less than or equal to zero") 105 | } 106 | if bufferPoolLimitMB <= 0 { 107 | return 0, errors.New("process limit MB cannot be less than or equal to zero") 108 | } 109 | processLimitBytes := bufferPoolLimitMB * 1024 * 1024 110 | return int(math.Floor(float64(processLimitBytes / receiveBufferSize))), nil 111 | } 112 | -------------------------------------------------------------------------------- /pkg/bufferpool/bufferpool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package bufferpool 11 | 12 | import ( 13 | "math" 14 | "testing" 15 | 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | type bufferPoolTestCase struct { 20 | processorSizeMB int 21 | bufferSizeKB int 22 | } 23 | 24 | func TestBufferPoolGet(t *testing.T) { 25 | testCases := []int{10, 200, 1000, 5000, 10000} 26 | for _, bufferLimit := range testCases { 27 | 28 | bufferSize := 256 * 1024 29 | 30 | bufferPool := Init(bufferLimit, bufferSize) 31 | 32 | // First Fetch 33 | buf := bufferPool.Get() 34 | 35 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), bufferLimit-1) 36 | assert.NotNil(t, buf) 37 | 38 | // Try to get all. Minus 1 due to fetch above 39 | for i := 0; i < bufferLimit-1; i++ { 40 | buf = bufferPool.Get() 41 | 42 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), bufferLimit-1-(i+1)) 43 | assert.NotNil(t, buf) 44 | } 45 | 46 | // No more buffer left hence returned nil 47 | buf = bufferPool.Get() 48 | 49 | assert.Nil(t, buf) 50 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), 0) 51 | } 52 | } 53 | 54 | func TestBufferReturn(t *testing.T) { 55 | bufferLimit := 10 56 | bufferSize := 256 * 1024 57 | bufferPool := Init(bufferLimit, bufferSize) 58 | buf := make([]byte, bufferSize) 59 | 60 | bufferPool.Return(&buf) 61 | 62 | // This return should be rejected as pool is already full 63 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), bufferLimit) 64 | 65 | // Fetch one and return buffer 66 | bufferPool.Get() 67 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), bufferLimit-1) 68 | 69 | bufferPool.Return(&buf) 70 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), bufferLimit) 71 | 72 | // Fetch two and return same buffer returned before which should be rejected 73 | returnedBuf1 := bufferPool.Get() 74 | returnedBuf2 := bufferPool.Get() 75 | 76 | assert.NotNil(t, returnedBuf1) 77 | assert.NotNil(t, returnedBuf2) 78 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), bufferLimit-2) 79 | 80 | bufferPool.Return(returnedBuf1) 81 | bufferPool.Return(returnedBuf1) 82 | 83 | assert.EqualValues(t, bufferPool.CurrentBuffersLen(), bufferLimit-1) 84 | } 85 | 86 | func TestBufferGetMultipleRoutine(t *testing.T) { 87 | testCases := []int{100, 1000, 2132} 88 | for _, bufferLimit := range testCases { 89 | bufferSize := 256 * 1024 90 | routines := 5 91 | pool := Init(bufferLimit, bufferSize) 92 | 93 | routineFunc := func(c chan int, pool *BufferPool) { 94 | count := 0 95 | for { 96 | buf := pool.Get() 97 | if buf == nil { 98 | break 99 | } 100 | count++ 101 | } 102 | c <- count 103 | } 104 | chans := make([]chan int, routines) 105 | for i := 0; i < routines; i++ { 106 | c := make(chan int) 107 | chans[i] = c 108 | go routineFunc(c, pool) 109 | } 110 | 111 | totalFetched := 0 112 | for i := 0; i < routines; i++ { 113 | bufFetched := <-chans[i] 114 | totalFetched += bufFetched 115 | } 116 | 117 | assert.EqualValues(t, bufferLimit, totalFetched) 118 | buf := pool.Get() 119 | assert.Nil(t, buf) 120 | } 121 | } 122 | 123 | func TestGetPoolBufferCount(t *testing.T) { 124 | testCases := []bufferPoolTestCase{ 125 | {processorSizeMB: 100, bufferSizeKB: 256}, 126 | {processorSizeMB: 16, bufferSizeKB: 125}, 127 | {processorSizeMB: 16, bufferSizeKB: 256}, 128 | {processorSizeMB: 250, bufferSizeKB: 512}, 129 | {processorSizeMB: 5, bufferSizeKB: 50}, 130 | } 131 | 132 | for _, testCase := range testCases { 133 | processSizeMB := testCase.processorSizeMB 134 | bufferSize := testCase.bufferSizeKB 135 | 136 | bufferCount, err := GetPoolBufferCount(processSizeMB, bufferSize) 137 | 138 | assert.Nil(t, err) 139 | expected := int(math.Floor(float64((processSizeMB * 1024 * 1024) / bufferSize))) 140 | assert.EqualValues(t, expected, bufferCount) 141 | } 142 | } 143 | 144 | func TestGetPoolBufferCountNegativeProcessorSize(t *testing.T) { 145 | bufferCount, err := GetPoolBufferCount(-123, 24512) 146 | 147 | assert.EqualValues(t, 0, bufferCount) 148 | assert.NotNil(t, err) 149 | assert.EqualValues(t, err.Error(), "process limit MB cannot be less than or equal to zero") 150 | } 151 | 152 | func TestGetPoolBufferCountNegativeBufferSize(t *testing.T) { 153 | bufferCount, err := GetPoolBufferCount(123, -24512) 154 | 155 | assert.EqualValues(t, 0, bufferCount) 156 | assert.NotNil(t, err) 157 | assert.EqualValues(t, err.Error(), "receive buffer size cannot be less than or equal to zero") 158 | } 159 | -------------------------------------------------------------------------------- /pkg/cfg.yaml: -------------------------------------------------------------------------------- 1 | # Maximum buffer size in MB (minimum 3). Choose 0 to use 1% of host memory. 2 | TotalBufferSizeMB: 0 3 | # Maximum number of concurrent calls to AWS X-Ray to upload segment documents. 4 | Concurrency: 8 5 | # Send segments to AWS X-Ray service in a specific region 6 | Region: "" 7 | # Change the X-Ray service endpoint to which the daemon sends segment documents. 8 | Endpoint: "" 9 | Socket: 10 | # Change the address and port on which the daemon listens for UDP packets containing segment documents. 11 | UDPAddress: "127.0.0.1:2000" 12 | # Change the address and port on which the daemon listens for HTTP requests to proxy to AWS X-Ray. 13 | TCPAddress: "127.0.0.1:2000" 14 | Logging: 15 | LogRotation: true 16 | # Change the log level, from most verbose to least: dev, debug, info, warn, error, prod (default). 17 | LogLevel: "prod" 18 | # Output logs to the specified file path. 19 | LogPath: "" 20 | # Turn on local mode to skip EC2 instance metadata check. 21 | LocalMode: false 22 | # Amazon Resource Name (ARN) of the AWS resource running the daemon. 23 | ResourceARN: "" 24 | # Assume an IAM role to upload segments to a different account. 25 | RoleARN: "" 26 | # Disable TLS certificate verification. 27 | NoVerifySSL: false 28 | # Upload segments to AWS X-Ray through a proxy. 29 | ProxyAddress: "" 30 | # Daemon configuration file format version. 31 | Version: 2 32 | -------------------------------------------------------------------------------- /pkg/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package cli 11 | 12 | import ( 13 | "flag" 14 | "fmt" 15 | "os" 16 | ) 17 | 18 | // Flag is used for cli parameters. 19 | type Flag struct { 20 | // A set of flags used for cli configuration. 21 | fs *flag.FlagSet 22 | 23 | // String array used to display flag information on cli. 24 | cliStrings []string 25 | } 26 | 27 | // NewFlag returns a new flag with provided flag name. 28 | func NewFlag(name string) *Flag { 29 | flag := &Flag{ 30 | cliStrings: make([]string, 0, 19), 31 | fs: flag.NewFlagSet(name, flag.ExitOnError), 32 | } 33 | return flag 34 | } 35 | 36 | // IntVarF defines 2 int flags for specified name and shortName with default value, and usage string. 37 | // The argument ptr points to an int variable in which to store the value of the flag. 38 | func (f *Flag) IntVarF(ptr *int, name string, shortName string, value int, usage string) { 39 | f.fs.IntVar(ptr, name, value, usage) 40 | f.fs.IntVar(ptr, shortName, value, usage) 41 | s := fmt.Sprintf("\t-%v\t--%v\t%v", shortName, name, usage) 42 | f.cliStrings = append(f.cliStrings, s) 43 | } 44 | 45 | // StringVarF defines 2 string flags for specified name and shortName, default value, and usage string. 46 | // The argument ptr points to a string variable in which to store the value of the flag. 47 | func (f *Flag) StringVarF(ptr *string, name string, shortName string, value string, usage string) { 48 | f.fs.StringVar(ptr, name, value, usage) 49 | f.fs.StringVar(ptr, shortName, value, usage) 50 | var s string 51 | if len(name) <= 4 { 52 | s = fmt.Sprintf("\t-%v\t--%v\t\t%v", shortName, name, usage) 53 | } else { 54 | s = fmt.Sprintf("\t-%v\t--%v\t%v", shortName, name, usage) 55 | } 56 | f.cliStrings = append(f.cliStrings, s) 57 | } 58 | 59 | // BoolVarF defines 2 bool flags with specified name and shortName, default value, and usage string. 60 | // The argument ptr points to a bool variable in which to store the value of the flag. 61 | func (f *Flag) BoolVarF(ptr *bool, name string, shortName string, value bool, usage string) { 62 | f.fs.BoolVar(ptr, name, value, usage) 63 | f.fs.BoolVar(ptr, shortName, value, usage) 64 | s := fmt.Sprintf("\t-%v\t--%v\t%v", shortName, name, usage) 65 | f.cliStrings = append(f.cliStrings, s) 66 | } 67 | 68 | // Format function formats Flag f for cli display. 69 | func (f *Flag) Format() []string { 70 | var cliDisplay = make([]string, 0, 20) 71 | s := fmt.Sprint("Usage: X-Ray [options]") 72 | cliDisplay = append(cliDisplay, s) 73 | for val := range f.cliStrings { 74 | cliDisplay = append(cliDisplay, f.cliStrings[val]) 75 | } 76 | s = fmt.Sprint("\t-h\t--help\t\tShow this screen") 77 | cliDisplay = append(cliDisplay, s) 78 | return cliDisplay 79 | } 80 | 81 | // ParseFlags parses flag definitions from the command line, which should not 82 | // include the command name. Must be called after all flags in the FlagSet 83 | // are defined and before flags are accessed by the program. 84 | // The return value will be ErrHelp if -help or -h were set but not defined. 85 | func (f *Flag) ParseFlags() { 86 | f.fs.Usage = func() { 87 | display := f.Format() 88 | for val := range display { 89 | fmt.Println(display[val]) 90 | } 91 | } 92 | f.fs.Parse(os.Args[1:]) 93 | } 94 | -------------------------------------------------------------------------------- /pkg/cli/cli_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package cli 11 | 12 | import ( 13 | "math/rand" 14 | "os" 15 | "strconv" 16 | "testing" 17 | 18 | "github.com/stretchr/testify/assert" 19 | ) 20 | 21 | type CLIArgs struct { 22 | StorageShort []string // store the shorthand flag 23 | StorageLong []string // store the flag name 24 | StorageUsage []string // store the flag usage 25 | StorageFlagInt []int // store the flag int value 26 | StorageFlagString []string // store the flag string value 27 | StorageFlagBool []bool // store the flag bool value 28 | } 29 | 30 | // generate the random string for given length 31 | func RandStr(strSize int) string { 32 | alphaNum := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 33 | var bytes = make([]byte, strSize) 34 | rand.Read(bytes) 35 | for i, b := range bytes { 36 | bytes[i] = alphaNum[b%byte(len(alphaNum))] 37 | } 38 | return string(bytes) 39 | } 40 | 41 | // store the given number into an variable 42 | func InitialVar(paras []int) []int { 43 | passLen := make([]int, 0, len(paras)) 44 | for i := 0; i < len(paras); i++ { 45 | passLen = append(passLen, paras[i]) 46 | } 47 | return passLen 48 | } 49 | 50 | // mock commandline input 51 | func SetUpInputs(args []string, f *Flag) { 52 | a := os.Args[1:] 53 | if args != nil { 54 | a = args 55 | } 56 | f.fs.Parse(a) 57 | } 58 | 59 | func (cli *CLIArgs) DefineFlagsArray(arrayLen int, strSize []int, strSizeFlag []int) *CLIArgs { 60 | cli.StorageShort = make([]string, 0, arrayLen) 61 | cli.StorageLong = make([]string, 0, arrayLen) 62 | cli.StorageUsage = make([]string, 0, arrayLen) 63 | cli.StorageFlagInt = make([]int, 0, arrayLen) 64 | cli.StorageFlagString = make([]string, 0, arrayLen) 65 | cli.StorageFlagBool = make([]bool, 0, arrayLen) 66 | mShort := make(map[string]bool, arrayLen) 67 | mLong := make(map[string]bool, arrayLen) 68 | mUsage := make(map[string]bool, arrayLen) 69 | for i := 0; i < len(strSize); i++ { 70 | for j := 0; j < arrayLen; j++ { 71 | if strSize[i] == strSizeFlag[0] { 72 | for { 73 | s := RandStr(strSize[i]) 74 | _, ok := mShort[s] 75 | if !ok { 76 | mShort[s] = true 77 | break 78 | } 79 | } 80 | } 81 | if strSize[i] == strSizeFlag[1] { 82 | for { 83 | s := RandStr(strSize[i]) 84 | _, ok := mLong[s] 85 | if !ok { 86 | mLong[s] = true 87 | break 88 | } 89 | } 90 | } 91 | if strSize[i] == strSizeFlag[2] { 92 | for { 93 | s := RandStr(strSize[i]) 94 | _, ok := mUsage[s] 95 | if !ok { 96 | mUsage[s] = true 97 | break 98 | } 99 | } 100 | } 101 | } 102 | } 103 | for k := range mShort { 104 | cli.StorageShort = append(cli.StorageShort, k) 105 | } 106 | for k := range mLong { 107 | cli.StorageLong = append(cli.StorageLong, k) 108 | } 109 | for k := range mUsage { 110 | cli.StorageUsage = append(cli.StorageUsage, k) 111 | } 112 | for i := 0; i < arrayLen; i++ { 113 | cli.StorageFlagInt = append(cli.StorageFlagInt, 0) 114 | } 115 | for i := 0; i < arrayLen; i++ { 116 | cli.StorageFlagString = append(cli.StorageFlagString, "&") 117 | } 118 | for i := 0; i < arrayLen; i++ { 119 | cli.StorageFlagBool = append(cli.StorageFlagBool, true) 120 | } 121 | return cli 122 | } 123 | 124 | func (cli *CLIArgs) InitialFlags(f *Flag) *CLIArgs { 125 | for i := 0; i < 10; i++ { 126 | f.IntVarF(&cli.StorageFlagInt[i], cli.StorageLong[i], cli.StorageShort[i], -1, cli.StorageUsage[i]) 127 | } 128 | for i := 10; i < 20; i++ { 129 | f.StringVarF(&cli.StorageFlagString[i-10], cli.StorageLong[i], cli.StorageShort[i], "*", cli.StorageUsage[i]) 130 | } 131 | for i := 20; i < 30; i++ { 132 | f.BoolVarF(&cli.StorageFlagBool[i-20], cli.StorageLong[i], cli.StorageShort[i], false, cli.StorageUsage[i]) 133 | } 134 | 135 | return cli 136 | } 137 | 138 | func TestSettingsFromFlags(t *testing.T) { 139 | f := NewFlag("Test Flag") 140 | paras := []int{1, 5, 10} // generate the random string, the length are 1, 5, 10 141 | varSize := InitialVar(paras) 142 | c := CLIArgs{} 143 | cli := c.DefineFlagsArray(30, paras, varSize) 144 | cli = c.InitialFlags(f) 145 | 146 | var num [10]string 147 | var str [10]string 148 | var bo [10]string 149 | input := make([]string, 0, 60) 150 | inputFlags := make([]string, 0, 30) 151 | inputFlagsValue := make([]string, 0, 30) 152 | 153 | // generate the commandline input 154 | for i := 0; i < 10; i++ { 155 | num[i] = strconv.Itoa(rand.Intn(100)) 156 | str[i] = RandStr(rand.Intn(5) + 1) 157 | bo[i] = strconv.FormatBool(true) 158 | } 159 | for i := 0; i < 30; i++ { 160 | if i < 10 { 161 | marked := "-" + cli.StorageShort[i] 162 | input = append(input, marked) 163 | inputFlags = append(inputFlags, marked) 164 | input = append(input, num[i]) 165 | inputFlagsValue = append(inputFlagsValue, num[i]) 166 | } 167 | if i >= 10 && i < 20 { 168 | marked := "-" + cli.StorageShort[i] 169 | input = append(input, marked) 170 | inputFlags = append(inputFlags, marked) 171 | input = append(input, str[i-10]) 172 | inputFlagsValue = append(inputFlagsValue, str[i-10]) 173 | 174 | } 175 | if i >= 20 && i < 30 { 176 | inputFlags = append(inputFlags, "-"+cli.StorageShort[i]) 177 | marked := "-" + cli.StorageShort[i] + "=" + bo[i-20] 178 | input = append(input, marked) 179 | inputFlagsValue = append(inputFlagsValue, bo[i-20]) 180 | } 181 | } 182 | 183 | // test the default value 184 | SetUpInputs([]string{""}, f) 185 | 186 | for i := 0; i < 30; i++ { 187 | if i < 10 { 188 | assert.Equal(t, -1, cli.StorageFlagInt[i], "Failed to get the default value") 189 | } 190 | if i >= 10 && i < 20 { 191 | assert.Equal(t, "*", cli.StorageFlagString[i-10], "Failed to get the default value") 192 | } 193 | if i >= 20 && i < 30 { 194 | assert.Equal(t, false, cli.StorageFlagBool[i-20], "Failed to get the default value") 195 | } 196 | } 197 | 198 | // test commandline parse value 199 | SetUpInputs(input, f) 200 | 201 | for i := 0; i < 30; i++ { 202 | if i < 10 { 203 | assert.Equal(t, inputFlagsValue[i], strconv.Itoa(cli.StorageFlagInt[i]), "Failed to parse the value") 204 | } 205 | if i >= 10 && i < 20 { 206 | assert.Equal(t, inputFlagsValue[i], cli.StorageFlagString[i-10], "Failed to parse the value") 207 | } 208 | if i >= 20 && i < 30 { 209 | assert.Equal(t, inputFlagsValue[i], strconv.FormatBool(cli.StorageFlagBool[i-20]), "Failed to parse the value") 210 | } 211 | } 212 | 213 | // test flag usage 214 | for i := 0; i < 30; i++ { 215 | assert.Equal(t, cli.StorageUsage[i], f.fs.Lookup(cli.StorageShort[i]).Usage, "Failed to give the usage of the flag") 216 | } 217 | 218 | // test the display of usage 219 | s := f.Format() 220 | for i := 0; i < 30; i++ { 221 | assert.Equal(t, f.cliStrings[i], s[i+1], "Failed to match the format") 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /pkg/conn/xray_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package conn 11 | 12 | import ( 13 | "os" 14 | "runtime" 15 | "strconv" 16 | "strings" 17 | "time" 18 | 19 | "github.com/aws/aws-sdk-go/aws" 20 | "github.com/aws/aws-sdk-go/aws/awserr" 21 | "github.com/aws/aws-sdk-go/aws/request" 22 | "github.com/aws/aws-sdk-go/aws/session" 23 | "github.com/aws/aws-sdk-go/service/xray" 24 | "github.com/aws/aws-xray-daemon/pkg/cfg" 25 | log "github.com/cihub/seelog" 26 | ) 27 | 28 | // Constant prefixes used to identify information in user-agent 29 | const agentPrefix = "xray-agent/xray-daemon/" 30 | const execEnvPrefix = " exec-env/" 31 | const osPrefix = " OS/" 32 | 33 | // XRay defines X-Ray api call structure. 34 | type XRay interface { 35 | PutTraceSegments(input *xray.PutTraceSegmentsInput) (*xray.PutTraceSegmentsOutput, error) 36 | PutTelemetryRecords(input *xray.PutTelemetryRecordsInput) (*xray.PutTelemetryRecordsOutput, error) 37 | } 38 | 39 | // XRayClient represents X-Ray client. 40 | type XRayClient struct { 41 | xRay *xray.XRay 42 | } 43 | 44 | // PutTraceSegments makes PutTraceSegments api call on X-Ray client. 45 | func (c *XRayClient) PutTraceSegments(input *xray.PutTraceSegmentsInput) (*xray.PutTraceSegmentsOutput, error) { 46 | return c.xRay.PutTraceSegments(input) 47 | } 48 | 49 | // PutTelemetryRecords makes PutTelemetryRecords api call on X-Ray client. 50 | func (c *XRayClient) PutTelemetryRecords(input *xray.PutTelemetryRecordsInput) (*xray.PutTelemetryRecordsOutput, error) { 51 | return c.xRay.PutTelemetryRecords(input) 52 | } 53 | 54 | // NewXRay creates a new instance of the XRay client with a aws configuration and session . 55 | func NewXRay(awsConfig *aws.Config, s *session.Session) XRay { 56 | x := xray.New(s, awsConfig) 57 | log.Debugf("Using Endpoint: %s", x.Endpoint) 58 | 59 | execEnv := os.Getenv("AWS_EXECUTION_ENV") 60 | if execEnv == "" { 61 | execEnv = "UNKNOWN" 62 | } 63 | 64 | osInformation := runtime.GOOS + "-" + runtime.GOARCH 65 | 66 | x.Handlers.Build.PushBackNamed(request.NamedHandler{ 67 | Name: "tracing.XRayVersionUserAgentHandler", 68 | Fn: request.MakeAddToUserAgentFreeFormHandler(agentPrefix + cfg.Version + execEnvPrefix + execEnv + osPrefix + osInformation), 69 | }) 70 | 71 | x.Handlers.Sign.PushFrontNamed(request.NamedHandler{ 72 | Name: "tracing.TimestampHandler", 73 | Fn: func(r *request.Request) { 74 | r.HTTPRequest.Header.Set("X-Amzn-Xray-Timestamp", strconv.FormatFloat(float64(time.Now().UnixNano())/float64(time.Second), 'f', 9, 64)) 75 | }, 76 | }) 77 | 78 | return &XRayClient{ 79 | xRay: x, 80 | } 81 | } 82 | 83 | // IsTimeoutError checks whether error is timeout error. 84 | func IsTimeoutError(err error) bool { 85 | awsError, ok := err.(awserr.Error) 86 | if ok { 87 | if strings.Contains(awsError.Error(), "net/http: request canceled") { 88 | return true 89 | } 90 | } 91 | return false 92 | } 93 | -------------------------------------------------------------------------------- /pkg/logger/log_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package logger 11 | 12 | import ( 13 | "io" 14 | "github.com/aws/aws-xray-daemon/pkg/cfg" 15 | 16 | log "github.com/cihub/seelog" 17 | ) 18 | 19 | // LoadLogConfig configures Logger. 20 | func LoadLogConfig(writer io.Writer, c *cfg.Config, loglevel string) { 21 | var level log.LogLevel 22 | 23 | switch c.Logging.LogLevel { 24 | case "dev": 25 | level = log.TraceLvl 26 | case "debug": 27 | level = log.DebugLvl 28 | case "info": 29 | level = log.InfoLvl 30 | case "warn": 31 | level = log.WarnLvl 32 | case "error": 33 | level = log.ErrorLvl 34 | case "prod": 35 | level = log.InfoLvl 36 | } 37 | 38 | if loglevel != c.Logging.LogLevel { 39 | switch loglevel { 40 | case "dev": 41 | level = log.TraceLvl 42 | case "debug": 43 | level = log.DebugLvl 44 | case "info": 45 | level = log.InfoLvl 46 | case "warn": 47 | level = log.WarnLvl 48 | case "error": 49 | level = log.ErrorLvl 50 | case "prod": 51 | level = log.InfoLvl 52 | } 53 | } 54 | 55 | logger, _ := log.LoggerFromWriterWithMinLevelAndFormat(writer, level, cfg.LogFormat) 56 | log.ReplaceLogger(logger) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package logger 11 | 12 | import ( 13 | "bytes" 14 | "fmt" 15 | "testing" 16 | "time" 17 | 18 | "github.com/aws/aws-xray-daemon/pkg/cfg" 19 | "github.com/aws/aws-xray-daemon/pkg/util" 20 | 21 | "github.com/cihub/seelog" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | type TestCase struct { 26 | Level seelog.LogLevel 27 | Message string 28 | Params []interface{} 29 | Output string 30 | } 31 | 32 | func generateTestCase(t *testing.T, level seelog.LogLevel, formatID string, message string, params ...interface{}) TestCase { 33 | testCase := TestCase{ 34 | Level: level, 35 | Message: message, 36 | Params: params, 37 | } 38 | var levelStr string 39 | switch level { 40 | case seelog.ErrorLvl: 41 | levelStr = "Error" 42 | 43 | case seelog.InfoLvl: 44 | levelStr = "Info" 45 | 46 | case seelog.DebugLvl: 47 | levelStr = "Debug" 48 | 49 | case seelog.WarnLvl: 50 | levelStr = "Warn" 51 | 52 | case seelog.TraceLvl: 53 | levelStr = "Trace" 54 | 55 | case seelog.CriticalLvl: 56 | levelStr = "Critical" 57 | 58 | default: 59 | assert.Fail(t, "Unexpected log level", level) 60 | } 61 | 62 | msg := fmt.Sprintf(testCase.Message, testCase.Params...) 63 | testCase.Output = fmt.Sprintf("%s [%v] %v\n", time.Now().Format(formatID), levelStr, msg) 64 | return testCase 65 | } 66 | 67 | func TestLogger(t *testing.T) { 68 | var testCases []TestCase 69 | 70 | formatID := "2006-01-02T15:04:05Z07:00" 71 | for _, logLevel := range []seelog.LogLevel{seelog.DebugLvl, seelog.InfoLvl, seelog.ErrorLvl, seelog.WarnLvl, seelog.TraceLvl, seelog.CriticalLvl} { 72 | testCases = append(testCases, generateTestCase(t, logLevel, formatID, "(some message without parameters)")) 73 | testCases = append(testCases, generateTestCase(t, logLevel, formatID, "(some message with %v as param)", []interface{}{"|a param|"})) 74 | } 75 | 76 | for _, testCase := range testCases { 77 | testLogger(t, testCase) 78 | } 79 | } 80 | 81 | func testLogger(t *testing.T, testCase TestCase) { 82 | // create seelog logger that outputs to buffer 83 | var out bytes.Buffer 84 | config := &cfg.Config{ 85 | Logging: struct { 86 | LogRotation *bool `yaml:"LogRotation"` 87 | LogLevel string `yaml:"LogLevel"` 88 | LogPath string `yaml:"LogPath"` 89 | }{ 90 | LogRotation: util.Bool(true), 91 | LogLevel: "dev", 92 | LogPath: "/var/tmp/xray.log", 93 | }, 94 | } 95 | // call loadlogconfig method under test 96 | loglevel := "dev" 97 | LoadLogConfig(&out, config, loglevel) 98 | // exercise logger 99 | switch testCase.Level { 100 | case seelog.ErrorLvl: 101 | if len(testCase.Params) > 0 { 102 | seelog.Errorf(testCase.Message, testCase.Params...) 103 | } else { 104 | seelog.Error(testCase.Message) 105 | } 106 | 107 | case seelog.InfoLvl: 108 | if len(testCase.Params) > 0 { 109 | seelog.Infof(testCase.Message, testCase.Params...) 110 | } else { 111 | seelog.Info(testCase.Message) 112 | } 113 | 114 | case seelog.DebugLvl: 115 | if len(testCase.Params) > 0 { 116 | seelog.Debugf(testCase.Message, testCase.Params...) 117 | } else { 118 | seelog.Debug(testCase.Message) 119 | } 120 | 121 | case seelog.WarnLvl: 122 | if len(testCase.Params) > 0 { 123 | seelog.Warnf(testCase.Message, testCase.Params...) 124 | } else { 125 | seelog.Warn(testCase.Message) 126 | } 127 | 128 | case seelog.TraceLvl: 129 | if len(testCase.Params) > 0 { 130 | seelog.Tracef(testCase.Message, testCase.Params...) 131 | } else { 132 | seelog.Trace(testCase.Message) 133 | } 134 | 135 | case seelog.CriticalLvl: 136 | if len(testCase.Params) > 0 { 137 | seelog.Criticalf(testCase.Message, testCase.Params...) 138 | } else { 139 | seelog.Critical(testCase.Message) 140 | } 141 | 142 | default: 143 | assert.Fail(t, "Unexpected log level", testCase.Level) 144 | } 145 | seelog.Flush() 146 | 147 | // check result 148 | assert.Equal(t, testCase.Output, out.String()) 149 | } 150 | -------------------------------------------------------------------------------- /pkg/processor/batchprocessor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package processor 11 | 12 | import ( 13 | "github.com/aws/aws-xray-daemon/pkg/conn" 14 | "github.com/aws/aws-xray-daemon/pkg/telemetry" 15 | "github.com/aws/aws-xray-daemon/pkg/util/timer" 16 | "math/rand" 17 | "regexp" 18 | "time" 19 | 20 | "github.com/aws/aws-sdk-go/service/xray" 21 | log "github.com/cihub/seelog" 22 | ) 23 | 24 | var /* const */ segIdRegexp = regexp.MustCompile(`\"id\":\"(.*?)\"`) 25 | var /* const */ traceIdRegexp = regexp.MustCompile(`\"trace_id\":\"(.*?)\"`) 26 | 27 | // Structure for trace segments batch. 28 | type segmentsBatch struct { 29 | // Boolean channel set to true when processing the batch segments is done. 30 | done chan bool 31 | 32 | // String slice of trace segments. 33 | batches chan []*string 34 | 35 | // Instance of XRay, used to send data to X-Ray service. 36 | xRay conn.XRay 37 | 38 | // Random generator, used for back off logic in case of exceptions. 39 | randGen *rand.Rand 40 | 41 | // Instance of timer. 42 | timer timer.Timer 43 | } 44 | 45 | func (s *segmentsBatch) send(batch []*string) { 46 | select { 47 | case s.batches <- batch: 48 | 49 | default: 50 | select { 51 | case batchTruncated := <-s.batches: 52 | telemetry.T.SegmentSpillover(int64(len(batchTruncated))) 53 | log.Warnf("Spilling over %v segments", len(batchTruncated)) 54 | 55 | default: 56 | log.Debug("Segment batch: channel is de-queued") 57 | } 58 | log.Debug("Segment batch: retrying batch") 59 | s.send(batch) 60 | } 61 | } 62 | 63 | func (s *segmentsBatch) poll() { 64 | for { 65 | batch, ok := <-s.batches 66 | if ok { 67 | params := &xray.PutTraceSegmentsInput{ 68 | TraceSegmentDocuments: batch, 69 | } 70 | start := time.Now() 71 | // send segment to X-Ray service. 72 | r, err := s.xRay.PutTraceSegments(params) 73 | if err != nil { 74 | telemetry.EvaluateConnectionError(err) 75 | log.Errorf("Sending segment batch failed with: %v", err) 76 | continue 77 | } else { 78 | telemetry.T.SegmentSent(int64(len(batch))) 79 | } 80 | elapsed := time.Since(start) 81 | 82 | if len(r.UnprocessedTraceSegments) != 0 { 83 | log.Infof("Sent batch of %d segments but had %d Unprocessed segments (%1.3f seconds)", len(batch), 84 | len(r.UnprocessedTraceSegments), elapsed.Seconds()) 85 | batchesMap := make(map[string]string) 86 | for i := 0; i < len(batch); i++ { 87 | segIdStrs := segIdRegexp.FindStringSubmatch(*batch[i]) 88 | if len(segIdStrs) != 2 { 89 | log.Debugf("Failed to match \"id\" in segment: %v", *batch[i]) 90 | continue 91 | } 92 | batchesMap[segIdStrs[1]] = *batch[i] 93 | } 94 | for _, unprocessedSegment := range r.UnprocessedTraceSegments { 95 | telemetry.T.SegmentRejected(1) 96 | // Print all segments since don't know which exact one is invalid. 97 | if unprocessedSegment.Id == nil { 98 | log.Debugf("Received nil unprocessed segment id from X-Ray service: %v", unprocessedSegment) 99 | log.Debugf("Content in this batch: %v", params) 100 | break 101 | } 102 | traceIdStrs := traceIdRegexp.FindStringSubmatch(batchesMap[*unprocessedSegment.Id]) 103 | if len(traceIdStrs) != 2 { 104 | log.Errorf("Unprocessed segment: %v", unprocessedSegment) 105 | } else { 106 | log.Errorf("Unprocessed trace %v, segment: %v", traceIdStrs[1], unprocessedSegment) 107 | } 108 | log.Debugf(batchesMap[*unprocessedSegment.Id]) 109 | } 110 | } else { 111 | log.Infof("Successfully sent batch of %d segments (%1.3f seconds)", len(batch), elapsed.Seconds()) 112 | } 113 | } else { 114 | log.Trace("Segment batch: done!") 115 | s.done <- true 116 | break 117 | } 118 | } 119 | } 120 | 121 | func (s *segmentsBatch) close() { 122 | close(s.batches) 123 | } 124 | -------------------------------------------------------------------------------- /pkg/processor/batchprocessor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package processor 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "strings" 16 | "testing" 17 | 18 | "github.com/aws/aws-sdk-go/service/xray" 19 | "github.com/aws/aws-xray-daemon/pkg/util/test" 20 | "github.com/stretchr/testify/assert" 21 | "github.com/stretchr/testify/mock" 22 | ) 23 | 24 | var doneMsg = "Segment batch: done!" 25 | 26 | type MockXRayClient struct { 27 | mock.Mock 28 | CallNoToPutTraceSegments int 29 | input *xray.PutTraceSegmentsInput 30 | } 31 | 32 | func (c *MockXRayClient) PutTraceSegments(input *xray.PutTraceSegmentsInput) (*xray.PutTraceSegmentsOutput, error) { 33 | c.input = input 34 | c.CallNoToPutTraceSegments++ 35 | args := c.Called(nil) 36 | errorStr := args.String(0) 37 | var err error 38 | output := &xray.PutTraceSegmentsOutput{} 39 | if errorStr == "Send unprocessed" { 40 | segmentID := "Test-Segment-Id-1242113" 41 | output.UnprocessedTraceSegments = append(output.UnprocessedTraceSegments, &xray.UnprocessedTraceSegment{Id: &segmentID}) 42 | } else if errorStr == "Send Invalid" { 43 | output.UnprocessedTraceSegments = append(output.UnprocessedTraceSegments, &xray.UnprocessedTraceSegment{Id: nil}) 44 | } else if errorStr != "" { 45 | err = errors.New(errorStr) 46 | } 47 | return output, err 48 | } 49 | 50 | func (c *MockXRayClient) PutTelemetryRecords(input *xray.PutTelemetryRecordsInput) (*xray.PutTelemetryRecordsOutput, error) { 51 | return nil, nil 52 | } 53 | 54 | func TestSendOneBatch(t *testing.T) { 55 | s := segmentsBatch{ 56 | batches: make(chan []*string, 1), 57 | } 58 | testMessage := "Test Message" 59 | batch := []*string{&testMessage} 60 | 61 | s.send(batch) 62 | 63 | returnedBatch := <-s.batches 64 | assert.EqualValues(t, len(returnedBatch), 1) 65 | 66 | batchString := *returnedBatch[0] 67 | assert.EqualValues(t, batchString, testMessage) 68 | } 69 | 70 | func TestSendBatchChannelTruncate(t *testing.T) { 71 | log := test.LogSetup() 72 | s := segmentsBatch{ 73 | batches: make(chan []*string, 1), 74 | } 75 | testMessage := "Test Message" 76 | batch := []*string{&testMessage} 77 | testMessage2 := "Test Message 2" 78 | batch2 := []*string{&testMessage2} 79 | 80 | s.send(batch) 81 | s.send(batch2) 82 | 83 | returnedBatch := <-s.batches 84 | 85 | assert.EqualValues(t, len(returnedBatch), 1) 86 | assert.EqualValues(t, *returnedBatch[0], testMessage2) 87 | assert.True(t, strings.Contains(log.Logs[0], "Spilling over")) 88 | assert.True(t, strings.Contains(log.Logs[1], "retrying batch")) 89 | } 90 | 91 | func TestPollSendSuccess(t *testing.T) { 92 | log := test.LogSetup() 93 | xRay := new(MockXRayClient) 94 | xRay.On("PutTraceSegments", nil).Return("").Once() 95 | s := segmentsBatch{ 96 | batches: make(chan []*string, 1), 97 | xRay: xRay, 98 | done: make(chan bool), 99 | } 100 | testMessage := "{\"id\":\"9472\"" 101 | batch := []*string{&testMessage} 102 | s.send(batch) 103 | 104 | go s.poll() 105 | close(s.batches) 106 | <-s.done 107 | 108 | assert.EqualValues(t, xRay.CallNoToPutTraceSegments, 1) 109 | assert.True(t, strings.Contains(log.Logs[0], fmt.Sprintf("Successfully sent batch of %v", 1))) 110 | assert.True(t, strings.Contains(log.Logs[1], doneMsg)) 111 | } 112 | 113 | func TestPutTraceSegmentsParameters(t *testing.T) { 114 | log := test.LogSetup() 115 | xRay := new(MockXRayClient) 116 | xRay.On("PutTraceSegments", nil).Return("").Once() 117 | 118 | s := segmentsBatch{ 119 | batches: make(chan []*string, 1), 120 | xRay: xRay, 121 | done: make(chan bool), 122 | } 123 | testMessage := "{\"id\":\"9472\"" 124 | batch := []*string{&testMessage} 125 | s.send(batch) 126 | 127 | go s.poll() 128 | 129 | close(s.batches) 130 | <-s.done 131 | actualInput := xRay.input 132 | 133 | expectedInput := &xray.PutTraceSegmentsInput{ 134 | TraceSegmentDocuments: batch, 135 | } 136 | 137 | assert.EqualValues(t, actualInput, expectedInput) 138 | assert.EqualValues(t, xRay.CallNoToPutTraceSegments, 1) 139 | assert.True(t, strings.Contains(log.Logs[0], fmt.Sprintf("Successfully sent batch of %v", 1))) 140 | assert.True(t, strings.Contains(log.Logs[1], doneMsg)) 141 | } 142 | 143 | func TestPollSendReturnUnprocessed(t *testing.T) { 144 | log := test.LogSetup() 145 | xRay := new(MockXRayClient) 146 | xRay.On("PutTraceSegments", nil).Return("Send unprocessed").Once() 147 | s := segmentsBatch{ 148 | batches: make(chan []*string, 1), 149 | xRay: xRay, 150 | done: make(chan bool), 151 | } 152 | testMessage := "{\"id\":\"9472\"" 153 | batch := []*string{&testMessage} 154 | s.send(batch) 155 | 156 | go s.poll() 157 | close(s.batches) 158 | <-s.done 159 | 160 | assert.EqualValues(t, xRay.CallNoToPutTraceSegments, 1) 161 | assert.True(t, strings.Contains(log.Logs[0], fmt.Sprintf("Sent batch of %v segments but had %v Unprocessed segments", 1, 1))) 162 | assert.True(t, strings.Contains(log.Logs[1], "Unprocessed segment")) 163 | } 164 | 165 | func TestPollSendReturnUnprocessedInvalid(t *testing.T) { 166 | log := test.LogSetup() 167 | xRay := new(MockXRayClient) 168 | xRay.On("PutTraceSegments", nil).Return("Send Invalid").Once() 169 | s := segmentsBatch{ 170 | batches: make(chan []*string, 1), 171 | xRay: xRay, 172 | done: make(chan bool), 173 | } 174 | testMessage := "{\"id\":\"9472\"" 175 | batch := []*string{&testMessage} 176 | s.send(batch) 177 | 178 | go s.poll() 179 | close(s.batches) 180 | <-s.done 181 | 182 | assert.EqualValues(t, xRay.CallNoToPutTraceSegments, 1) 183 | assert.True(t, strings.Contains(log.Logs[0], fmt.Sprintf("Sent batch of %v segments but had %v Unprocessed segments", 1, 1))) 184 | assert.True(t, strings.Contains(log.Logs[1], "Received nil unprocessed segment id from X-Ray service")) 185 | } 186 | -------------------------------------------------------------------------------- /pkg/processor/processor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package processor 11 | 12 | import ( 13 | "sync/atomic" 14 | "time" 15 | 16 | log "github.com/cihub/seelog" 17 | 18 | "github.com/aws/aws-xray-daemon/pkg/bufferpool" 19 | "github.com/aws/aws-xray-daemon/pkg/ringbuffer" 20 | "github.com/aws/aws-xray-daemon/pkg/tracesegment" 21 | 22 | "github.com/aws/aws-xray-daemon/pkg/cfg" 23 | "github.com/aws/aws-xray-daemon/pkg/conn" 24 | "github.com/aws/aws-xray-daemon/pkg/util/timer" 25 | "math/rand" 26 | "os" 27 | 28 | "github.com/aws/aws-sdk-go/aws" 29 | "github.com/aws/aws-sdk-go/aws/session" 30 | ) 31 | 32 | // Processor buffers segments and send to X-Ray service. 33 | type Processor struct { 34 | // Boolean channel, set to true when processor has no segments in priority and standard ring buffer. 35 | Done chan bool 36 | 37 | // Ring buffer to store trace segments. 38 | std *ringbuffer.RingBuffer 39 | 40 | // Buffer pool instance. 41 | pool *bufferpool.BufferPool 42 | 43 | // Counter for segments received. 44 | count uint64 45 | 46 | // timer client used for setting idle timer. 47 | timerClient timer.Timer 48 | 49 | // segmentsBatch is used to process received segments batch. 50 | traceSegmentsBatch *segmentsBatch 51 | 52 | // Number of go routines to spawn for traceSegmentsBatch.poll(). 53 | batchProcessorCount int 54 | 55 | // Channel for Time. 56 | idleTimer <-chan time.Time 57 | 58 | // Size of the batch segments processed by Processor. 59 | batchSize int 60 | 61 | // Idle timeout in milliseconds used while sending batch segments. 62 | sendIdleTimeout time.Duration 63 | } 64 | 65 | // New creates new instance of Processor. 66 | func New(awsConfig *aws.Config, s *session.Session, segmentBatchProcessorCount int, std *ringbuffer.RingBuffer, 67 | pool *bufferpool.BufferPool, c *cfg.ParameterConfig) *Processor { 68 | batchesChan := make(chan []*string, c.Processor.BatchProcessorQueueSize) 69 | segmentBatchDoneChan := make(chan bool) 70 | tsb := &segmentsBatch{ 71 | batches: batchesChan, 72 | done: segmentBatchDoneChan, 73 | randGen: rand.New(rand.NewSource(time.Now().UnixNano())), 74 | timer: &timer.Client{}, 75 | } 76 | x := conn.NewXRay(awsConfig, s) 77 | if x == nil { 78 | log.Error("X-Ray client returned nil") 79 | os.Exit(1) 80 | } 81 | tsb.xRay = x 82 | doneChan := make(chan bool) 83 | log.Debugf("Batch size: %v", c.Processor.BatchSize) 84 | p := &Processor{ 85 | Done: doneChan, 86 | std: std, 87 | pool: pool, 88 | count: 0, 89 | timerClient: &timer.Client{}, 90 | batchProcessorCount: segmentBatchProcessorCount, 91 | traceSegmentsBatch: tsb, 92 | batchSize: c.Processor.BatchSize, 93 | sendIdleTimeout: time.Millisecond * time.Duration(c.Processor.IdleTimeoutMillisecond), 94 | } 95 | 96 | for i := 0; i < p.batchProcessorCount; i++ { 97 | go p.traceSegmentsBatch.poll() 98 | } 99 | 100 | go p.poll() 101 | 102 | return p 103 | } 104 | 105 | func (p *Processor) poll() { 106 | batch := make([]*tracesegment.TraceSegment, 0, p.batchSize) 107 | p.SetIdleTimer() 108 | 109 | for { 110 | select { 111 | case segment, ok := <-p.std.Channel: 112 | if ok { 113 | batch = p.receiveTraceSegment(segment, batch) 114 | } else { 115 | p.std.Empty = true 116 | } 117 | case <-p.idleTimer: 118 | if len(batch) > 0 { 119 | log.Debug("processor: sending partial batch") 120 | batch = p.sendBatchAsync(batch) 121 | } else { 122 | p.SetIdleTimer() 123 | } 124 | } 125 | 126 | if p.std.Empty { 127 | break 128 | } 129 | } 130 | 131 | if len(batch) > 0 { 132 | batch = p.sendBatchAsync(batch) 133 | } 134 | p.traceSegmentsBatch.close() 135 | for i := 0; i < p.batchProcessorCount; i++ { 136 | <-p.traceSegmentsBatch.done 137 | } 138 | log.Debug("processor: done!") 139 | p.Done <- true 140 | } 141 | 142 | func (p *Processor) receiveTraceSegment(ts *tracesegment.TraceSegment, batch []*tracesegment.TraceSegment) []*tracesegment.TraceSegment { 143 | atomic.AddUint64(&p.count, 1) 144 | batch = append(batch, ts) 145 | 146 | if len(batch) >= p.batchSize { 147 | log.Debug("processor: sending complete batch") 148 | batch = p.sendBatchAsync(batch) 149 | } else if p.pool.CurrentBuffersLen() == 0 { 150 | log.Debug("processor: sending partial batch due to load on buffer pool") 151 | batch = p.sendBatchAsync(batch) 152 | } 153 | 154 | return batch 155 | } 156 | 157 | // Resizing slice doesn't make a copy of the underlying array and hence memory is not 158 | // garbage collected. (http://blog.golang.org/go-slices-usage-and-internals) 159 | func (p *Processor) flushBatch(batch []*tracesegment.TraceSegment) []*tracesegment.TraceSegment { 160 | for i := 0; i < len(batch); i++ { 161 | batch[i] = nil 162 | } 163 | batch = batch[0:0] 164 | 165 | return batch 166 | } 167 | 168 | func (p *Processor) sendBatchAsync(batch []*tracesegment.TraceSegment) []*tracesegment.TraceSegment { 169 | log.Debugf("processor: segment batch size: %d. capacity: %d", len(batch), cap(batch)) 170 | 171 | segmentDocuments := []*string{} 172 | for _, segment := range batch { 173 | rawBytes := *segment.Raw 174 | x := string(rawBytes[:]) 175 | segmentDocuments = append(segmentDocuments, &x) 176 | p.pool.Return(segment.PoolBuf) 177 | } 178 | p.traceSegmentsBatch.send(segmentDocuments) 179 | // Reset Idle Timer 180 | p.SetIdleTimer() 181 | return p.flushBatch(batch) 182 | } 183 | 184 | // ProcessedCount returns number of trace segment received. 185 | func (p *Processor) ProcessedCount() uint64 { 186 | return atomic.LoadUint64(&p.count) 187 | } 188 | 189 | // SetIdleTimer sets idle timer for the processor instance. 190 | func (p *Processor) SetIdleTimer() { 191 | p.idleTimer = p.timerClient.After(p.sendIdleTimeout) 192 | } 193 | -------------------------------------------------------------------------------- /pkg/processor/processor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package processor 11 | 12 | import ( 13 | "fmt" 14 | "github.com/aws/aws-xray-daemon/pkg/bufferpool" 15 | "github.com/aws/aws-xray-daemon/pkg/ringbuffer" 16 | "github.com/aws/aws-xray-daemon/pkg/telemetry" 17 | "github.com/aws/aws-xray-daemon/pkg/tracesegment" 18 | "github.com/aws/aws-xray-daemon/pkg/util/test" 19 | "strings" 20 | "testing" 21 | "time" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func init() { 27 | telemetry.T = telemetry.GetTestTelemetry() 28 | } 29 | 30 | func TestFlushBatch(t *testing.T) { 31 | variousTests := []int{0, 10, 100, 324} 32 | for _, testCase := range variousTests { 33 | processor := Processor{} 34 | segments := make([]*tracesegment.TraceSegment, testCase) 35 | for i := 0; i < testCase; i++ { 36 | segmentVal := tracesegment.GetTestTraceSegment() 37 | segments[i] = &segmentVal 38 | } 39 | 40 | segmentsFlushed := processor.flushBatch(segments) 41 | 42 | assert.Equal(t, len(segmentsFlushed), 0) 43 | assert.Equal(t, cap(segmentsFlushed), testCase) 44 | for _, segmentVal := range segmentsFlushed { 45 | assert.Nil(t, segmentVal) 46 | } 47 | } 48 | } 49 | 50 | func TestSendBatchSuccess(t *testing.T) { 51 | timer := test.MockTimerClient{} 52 | variousTests := []int{0, 50, 40} 53 | for _, testCase := range variousTests { 54 | writer := test.LogSetup() 55 | segments := make([]*tracesegment.TraceSegment, testCase) 56 | for i := 0; i < testCase; i++ { 57 | segmentVal := tracesegment.GetTestTraceSegment() 58 | segments[i] = &segmentVal 59 | } 60 | processor := Processor{ 61 | pool: bufferpool.Init(testCase+1, 100), 62 | timerClient: &timer, 63 | traceSegmentsBatch: &segmentsBatch{ 64 | batches: make(chan []*string, 1), 65 | }, 66 | } 67 | // Empty Pool 68 | for i := 0; i < testCase+1; i++ { 69 | processor.pool.Get() 70 | } 71 | assert.EqualValues(t, processor.pool.CurrentBuffersLen(), 0) 72 | 73 | returnedSegment := processor.sendBatchAsync(segments) 74 | 75 | assert.EqualValues(t, cap(returnedSegment), cap(segments)) 76 | assert.EqualValues(t, len(returnedSegment), 0) 77 | for _, segmentVal := range returnedSegment { 78 | assert.Nil(t, segmentVal) 79 | } 80 | assert.True(t, strings.Contains(writer.Logs[0], fmt.Sprintf("segment batch size: %v", testCase))) 81 | select { 82 | case batch := <-processor.traceSegmentsBatch.batches: 83 | assert.NotNil(t, batch) 84 | default: 85 | assert.Fail(t, "Expected batch to be in batch channel") 86 | } 87 | // Asserting the buffer pool was returned 88 | assert.EqualValues(t, processor.pool.CurrentBuffersLen(), testCase) 89 | } 90 | } 91 | 92 | func TestPollingFewSegmentsExit(t *testing.T) { 93 | pool := bufferpool.Init(1, 100) 94 | stdChan := ringbuffer.New(20, pool) 95 | doneChan := make(chan bool) 96 | timer := &test.MockTimerClient{} 97 | writer := test.LogSetup() 98 | processor := &Processor{ 99 | timerClient: timer, 100 | std: stdChan, 101 | count: 0, 102 | Done: doneChan, 103 | pool: pool, 104 | traceSegmentsBatch: &segmentsBatch{ 105 | batches: make(chan []*string, 1), 106 | }, 107 | sendIdleTimeout: time.Second, 108 | batchSize: 50, 109 | } 110 | 111 | go processor.poll() 112 | 113 | // Increment for Send Batch to proceed 114 | timer.Advance(time.Duration(10)) 115 | segment := tracesegment.GetTestTraceSegment() 116 | stdChan.Send(&segment) 117 | stdChan.Close() 118 | 119 | <-processor.Done 120 | 121 | assert.EqualValues(t, processor.ProcessedCount(), 1) 122 | assert.True(t, strings.Contains(writer.Logs[0], "segment batch size: 1")) 123 | assert.True(t, strings.Contains(writer.Logs[1], "processor: done!")) 124 | } 125 | 126 | func TestPollingFewSegmentsIdleTimeout(t *testing.T) { 127 | pool := bufferpool.Init(1, 100) 128 | stdChan := ringbuffer.New(20, pool) 129 | doneChan := make(chan bool) 130 | timer := &test.MockTimerClient{} 131 | 132 | writer := test.LogSetup() 133 | processor := &Processor{ 134 | timerClient: timer, 135 | std: stdChan, 136 | count: 0, 137 | Done: doneChan, 138 | pool: pool, 139 | traceSegmentsBatch: &segmentsBatch{ 140 | batches: make(chan []*string, 1), 141 | }, 142 | sendIdleTimeout: time.Second, 143 | batchSize: 50, 144 | } 145 | 146 | go processor.poll() 147 | 148 | // Sleep to process go routine initialization 149 | time.Sleep(time.Millisecond) 150 | // Adding segment to priChan 151 | segment := tracesegment.GetTestTraceSegment() 152 | stdChan.Send(&segment) 153 | // Sleep to see to it the chan is processed before timeout is triggered 154 | time.Sleep(time.Millisecond) 155 | // Trigger Ideal Timeout to trigger PutSegments 156 | timer.Advance(processor.sendIdleTimeout) 157 | time.Sleep(time.Millisecond) 158 | // Sleep so that time.After trigger batch send and not closing of the channel 159 | stdChan.Close() 160 | 161 | <-doneChan 162 | 163 | assert.True(t, strings.Contains(writer.Logs[0], "sending partial batch")) 164 | assert.True(t, strings.Contains(writer.Logs[1], "segment batch size: 1")) 165 | assert.True(t, strings.Contains(writer.Logs[2], "processor: done!")) 166 | } 167 | 168 | func TestPollingBatchBufferFull(t *testing.T) { 169 | batchSize := 50 170 | pool := bufferpool.Init(1, 100) 171 | // Setting stdChan to batchSize so that it does not spill over 172 | stdChan := ringbuffer.New(batchSize, pool) 173 | doneChan := make(chan bool) 174 | timer := &test.MockTimerClient{} 175 | 176 | writer := test.LogSetup() 177 | segmentProcessorCount := 1 178 | processor := &Processor{ 179 | timerClient: timer, 180 | std: stdChan, 181 | count: 0, 182 | Done: doneChan, 183 | batchProcessorCount: segmentProcessorCount, 184 | pool: pool, 185 | traceSegmentsBatch: &segmentsBatch{ 186 | batches: make(chan []*string, 1), 187 | done: make(chan bool), 188 | }, 189 | batchSize: batchSize, 190 | } 191 | 192 | go processor.poll() 193 | 194 | for i := 0; i < batchSize; i++ { 195 | // Adding segment to priChan 196 | segment := tracesegment.GetTestTraceSegment() 197 | stdChan.Send(&segment) 198 | 199 | } 200 | stdChan.Close() 201 | processor.traceSegmentsBatch.done <- true 202 | 203 | <-doneChan 204 | 205 | assert.EqualValues(t, processor.ProcessedCount(), batchSize) 206 | assert.True(t, strings.Contains(writer.Logs[0], "sending complete batch")) 207 | assert.True(t, strings.Contains(writer.Logs[1], fmt.Sprintf("segment batch size: %v", batchSize))) 208 | assert.True(t, strings.Contains(writer.Logs[2], "processor: done!")) 209 | } 210 | 211 | func TestPollingBufferPoolExhaustedForcingSent(t *testing.T) { 212 | pool := bufferpool.Init(1, 100) 213 | batchSize := 50 214 | // Exhaust the buffer pool 215 | pool.Get() 216 | assert.EqualValues(t, pool.CurrentBuffersLen(), 0) 217 | stdChan := ringbuffer.New(batchSize, pool) 218 | doneChan := make(chan bool) 219 | timer := &test.MockTimerClient{} 220 | 221 | writer := test.LogSetup() 222 | segmentProcessorCount := 1 223 | processor := &Processor{ 224 | timerClient: timer, 225 | std: stdChan, 226 | count: 0, 227 | Done: doneChan, 228 | batchProcessorCount: segmentProcessorCount, 229 | pool: pool, 230 | traceSegmentsBatch: &segmentsBatch{ 231 | batches: make(chan []*string, 1), 232 | done: make(chan bool), 233 | }, 234 | sendIdleTimeout: time.Second, 235 | batchSize: batchSize, 236 | } 237 | 238 | go processor.poll() 239 | 240 | segment := tracesegment.GetTestTraceSegment() 241 | stdChan.Send(&segment) 242 | stdChan.Close() 243 | processor.traceSegmentsBatch.done <- true 244 | 245 | <-doneChan 246 | 247 | assert.EqualValues(t, processor.ProcessedCount(), 1) 248 | assert.True(t, strings.Contains(writer.Logs[0], "sending partial batch due to load on buffer pool")) 249 | assert.True(t, strings.Contains(writer.Logs[1], fmt.Sprintf("segment batch size: %v", 1))) 250 | assert.True(t, strings.Contains(writer.Logs[2], "processor: done!")) 251 | } 252 | 253 | func TestPollingIdleTimerIsInitiatedAfterElapseWithNoSegments(t *testing.T) { 254 | timer := &test.MockTimerClient{} 255 | pool := bufferpool.Init(1, 100) 256 | batchSize := 50 257 | stdChan := ringbuffer.New(batchSize, pool) 258 | processor := &Processor{ 259 | Done: make(chan bool), 260 | timerClient: timer, 261 | std: stdChan, 262 | pool: pool, 263 | traceSegmentsBatch: &segmentsBatch{ 264 | batches: make(chan []*string, 1), 265 | }, 266 | sendIdleTimeout: time.Second, 267 | batchSize: batchSize, 268 | } 269 | 270 | go processor.poll() 271 | 272 | // Sleep for routine to be initiated 273 | time.Sleep(time.Millisecond) 274 | // Trigger Idle Timeout 275 | timer.Advance(processor.sendIdleTimeout) 276 | // sleep so that routine exist after timeout is tiggered 277 | time.Sleep(time.Millisecond) 278 | stdChan.Close() 279 | <-processor.Done 280 | 281 | // Called twice once at poll start and then after the timeout was triggered 282 | assert.EqualValues(t, timer.AfterCalledTimes(), 2) 283 | } 284 | -------------------------------------------------------------------------------- /pkg/profiler/profiler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package profiler 11 | 12 | import ( 13 | "os" 14 | "runtime/pprof" 15 | 16 | log "github.com/cihub/seelog" 17 | ) 18 | 19 | // EnableCPUProfile enables CPU profiling. 20 | func EnableCPUProfile(cpuProfile *string) { 21 | if *cpuProfile != "" { 22 | f, err := os.Create(*cpuProfile) 23 | if err != nil { 24 | log.Errorf("error: %v", err) 25 | } 26 | pprof.StartCPUProfile(f) 27 | log.Info("Start CPU Profiling") 28 | } 29 | } 30 | 31 | // MemSnapShot creates memory profile. 32 | func MemSnapShot(memProfile *string) { 33 | if *memProfile != "" { 34 | f, err := os.Create(*memProfile) 35 | if err != nil { 36 | log.Errorf("Could not create memory profile: %v", err) 37 | } 38 | if err := pprof.WriteHeapProfile(f); err != nil { 39 | log.Errorf("Could not write memory profile: %v", err) 40 | } 41 | err = f.Close() 42 | if err != nil { 43 | log.Errorf("unable to close file: %v", err) 44 | } 45 | log.Info("Finish memory profiling") 46 | return 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pkg/proxy/server.go: -------------------------------------------------------------------------------- 1 | // Package proxy provides an http server to act as a signing proxy for SDKs calling AWS X-Ray APIs 2 | package proxy 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net" 11 | "net/http" 12 | "net/http/httputil" 13 | "net/url" 14 | "os" 15 | "time" 16 | 17 | "github.com/aws/aws-sdk-go/aws" 18 | "github.com/aws/aws-sdk-go/aws/endpoints" 19 | "github.com/aws/aws-sdk-go/aws/session" 20 | "github.com/aws/aws-sdk-go/aws/signer/v4" 21 | "github.com/aws/aws-xray-daemon/pkg/cfg" 22 | "github.com/aws/aws-xray-daemon/pkg/conn" 23 | log "github.com/cihub/seelog" 24 | ) 25 | 26 | const service = "xray" 27 | const connHeader = "Connection" 28 | 29 | // Server represents HTTP server. 30 | type Server struct { 31 | *http.Server 32 | } 33 | 34 | // NewServer returns a proxy server listening on the given address. 35 | // Requests are forwarded to the endpoint in the given config. 36 | // Requests are signed using credentials from the given config. 37 | func NewServer(cfg *cfg.Config, awsCfg *aws.Config, sess *session.Session) (*Server, error) { 38 | _, err := net.ResolveTCPAddr("tcp", cfg.Socket.TCPAddress) 39 | if err != nil { 40 | log.Errorf("%v", err) 41 | os.Exit(1) 42 | } 43 | endPoint, er := getServiceEndpoint(awsCfg) 44 | 45 | if er != nil { 46 | return nil, fmt.Errorf("%v", er) 47 | } 48 | 49 | log.Infof("HTTP Proxy server using X-Ray Endpoint : %v", endPoint) 50 | 51 | // Parse url from endpoint 52 | url, err := url.Parse(endPoint) 53 | if err != nil { 54 | return nil, fmt.Errorf("unable to parse xray endpoint: %v", err) 55 | } 56 | 57 | signer := &v4.Signer{ 58 | Credentials: sess.Config.Credentials, 59 | } 60 | 61 | transport := conn.ProxyServerTransport(cfg) 62 | 63 | // Reverse proxy handler 64 | handler := &httputil.ReverseProxy{ 65 | Transport: transport, 66 | 67 | // Handler for modifying and forwarding requests 68 | Director: func(req *http.Request) { 69 | if req != nil && req.URL != nil { 70 | log.Debugf("Received request on HTTP Proxy server : %s", req.URL.String()) 71 | } else { 72 | log.Debug("Request/Request.URL received on HTTP Proxy server is nil") 73 | } 74 | 75 | // Remove connection header before signing request, otherwise the 76 | // reverse-proxy will remove the header before forwarding to X-Ray 77 | // resulting in a signed header being missing from the request. 78 | req.Header.Del(connHeader) 79 | 80 | // Set req url to xray endpoint 81 | req.URL.Scheme = url.Scheme 82 | req.URL.Host = url.Host 83 | req.Host = url.Host 84 | 85 | // Consume body and convert to io.ReadSeeker for signer to consume 86 | body, err := consume(req.Body) 87 | if err != nil { 88 | log.Errorf("Unable to consume request body: %v", err) 89 | 90 | // Forward unsigned request 91 | return 92 | } 93 | 94 | // Sign request. signer.Sign() also repopulates the request body. 95 | _, err = signer.Sign(req, body, service, *awsCfg.Region, time.Now()) 96 | if err != nil { 97 | log.Errorf("Unable to sign request: %v", err) 98 | } 99 | }, 100 | } 101 | 102 | server := &http.Server{ 103 | Addr: cfg.Socket.TCPAddress, 104 | Handler: handler, 105 | } 106 | 107 | p := &Server{server} 108 | 109 | return p, nil 110 | } 111 | 112 | // consume readsAll() the body and creates a new io.ReadSeeker from the content. v4.Signer 113 | // requires an io.ReadSeeker to be able to sign requests. May return a nil io.ReadSeeker. 114 | func consume(body io.ReadCloser) (io.ReadSeeker, error) { 115 | var buf []byte 116 | 117 | // Return nil ReadSeeker if body is nil 118 | if body == nil { 119 | return nil, nil 120 | } 121 | 122 | // Consume body 123 | buf, err := ioutil.ReadAll(body) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | return bytes.NewReader(buf), nil 129 | } 130 | 131 | // Serve starts server. 132 | func (s *Server) Serve() { 133 | log.Infof("Starting proxy http server on %s", s.Addr) 134 | if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { 135 | log.Errorf("proxy http server failed to listen: %v", err) 136 | } 137 | } 138 | 139 | // Close stops server. 140 | func (s *Server) Close() { 141 | err := s.Server.Close() 142 | if err != nil { 143 | log.Errorf("unable to close the server: %v", err) 144 | } 145 | } 146 | 147 | // getServiceEndpoint returns X-Ray service endpoint. 148 | // It is guaranteed that awsCfg config instance is non-nil and the region value is non nil or non empty in awsCfg object. 149 | // Currently the caller takes care of it. 150 | func getServiceEndpoint(awsCfg *aws.Config) (string, error) { 151 | if awsCfg.Endpoint == nil || *awsCfg.Endpoint == "" { 152 | if awsCfg.Region == nil || *awsCfg.Region == "" { 153 | return "", errors.New("unable to generate endpoint from region with nil value") 154 | } 155 | resolved, err := endpoints.DefaultResolver().EndpointFor(service, *awsCfg.Region, setResolverConfig()) 156 | if err != nil { 157 | return "", err 158 | } 159 | 160 | return resolved.URL, err 161 | } 162 | return *awsCfg.Endpoint, nil 163 | } 164 | 165 | func setResolverConfig() func(*endpoints.Options) { 166 | return func(p *endpoints.Options) { 167 | p.ResolveUnknownService = true 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /pkg/proxy/server_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httputil" 8 | "net/url" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/aws/aws-sdk-go/aws" 13 | "github.com/aws/aws-sdk-go/aws/credentials" 14 | "github.com/aws/aws-sdk-go/aws/session" 15 | "github.com/aws/aws-xray-daemon/pkg/cfg" 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | // Assert that consume returns a ReadSeeker with the same content as the 20 | // ReadCloser passed in. 21 | func TestConsume(t *testing.T) { 22 | // Create an io.Reader 23 | r := strings.NewReader("Content") 24 | 25 | // Create an io.ReadCloser 26 | rc := ioutil.NopCloser(r) 27 | 28 | // Consume ReadCloser and create ReadSeeker 29 | rs, err := consume(rc) 30 | assert.Nil(t, err) 31 | 32 | // Read from ReadSeeker 33 | bytes, err := ioutil.ReadAll(rs) 34 | assert.Nil(t, err) 35 | 36 | // Assert contents of bytes are same as contents of original Reader 37 | assert.Equal(t, "Content", string(bytes)) 38 | } 39 | 40 | // Assert that consume returns a nil ReadSeeker when a nil ReadCloser is passed in 41 | func TestConsumeNilBody(t *testing.T) { 42 | // Create a nil io.ReadCloser 43 | var rc io.ReadCloser 44 | 45 | // Consume ReadCloser and create ReadSeeker 46 | rs, err := consume(rc) 47 | assert.Nil(t, err) 48 | assert.Nil(t, rs) 49 | } 50 | 51 | // Assert that Director modifies the passed in http.Request 52 | func TestDirector(t *testing.T) { 53 | // Create dummy credentials to sign with 54 | cred := credentials.NewStaticCredentials("id", "secret", "token") 55 | 56 | // Create dummy aws Config 57 | awsCfg := &aws.Config{ 58 | Endpoint: aws.String("https://xray.us-east-1.amazonaws.com"), 59 | Region: aws.String("us-east-1"), 60 | Credentials: cred, 61 | } 62 | 63 | // Create dummy aws Session 64 | sess := &session.Session{ 65 | Config: awsCfg, 66 | } 67 | 68 | // Create proxy server 69 | s, err := NewServer(cfg.DefaultConfig(), awsCfg, sess) 70 | assert.Nil(t, err) 71 | 72 | // Extract director from server 73 | d := s.Handler.(*httputil.ReverseProxy).Director 74 | 75 | // Create http request to pass to director 76 | url, err := url.Parse("http://127.0.0.1:2000") 77 | assert.Nil(t, err) 78 | 79 | header := map[string][]string{ 80 | "Connection": []string{}, 81 | } 82 | 83 | req := &http.Request{ 84 | URL: url, 85 | Host: "127.0.0.1", 86 | Header: header, 87 | Body: ioutil.NopCloser(strings.NewReader("Body")), 88 | } 89 | 90 | // Apply director to request 91 | d(req) 92 | 93 | // Assert that the url was changed to point to AWS X-Ray 94 | assert.Equal(t, "https", req.URL.Scheme) 95 | assert.Equal(t, "xray.us-east-1.amazonaws.com", req.URL.Host) 96 | assert.Equal(t, "xray.us-east-1.amazonaws.com", req.Host) 97 | 98 | // Assert that additional headers were added by the signer 99 | assert.Contains(t, req.Header, "Authorization") 100 | assert.Contains(t, req.Header, "X-Amz-Security-Token") 101 | assert.Contains(t, req.Header, "X-Amz-Date") 102 | assert.NotContains(t, req.Header, "Connection") 103 | } 104 | 105 | // Fetching endpoint from aws config instance 106 | func TestEndpoint1(t *testing.T) { 107 | e := "https://xray.us-east-1.amazonaws.com" 108 | awsCfg := &aws.Config{ 109 | Endpoint: aws.String(e), // Endpoint value has higher priority than region value 110 | Region: aws.String("us-west-1"), 111 | } 112 | result, err := getServiceEndpoint(awsCfg) 113 | assert.Equal(t, e, result, "Fetching endpoint from config instance") 114 | assert.Nil(t, err) 115 | } 116 | 117 | // Generating endpoint from region value of awsCfg instance 118 | func TestEndpoint2(t *testing.T) { 119 | e := "https://xray.us-west-1.amazonaws.com" 120 | awsCfg := &aws.Config{ 121 | Region: aws.String("us-west-1"), // No endpoint 122 | } 123 | result, err := getServiceEndpoint(awsCfg) 124 | assert.Equal(t, e, result, "Fetching endpoint from region") 125 | assert.Nil(t, err) 126 | } 127 | 128 | // Error received when no endpoint and region value present in awsCfg instance 129 | func TestEndpoint3(t *testing.T) { 130 | awsCfg := &aws.Config{ 131 | // No endpoint and region value 132 | } 133 | result, err := getServiceEndpoint(awsCfg) 134 | assert.Equal(t, "", result, "Endpoint cannot be created") 135 | assert.NotNil(t, err) 136 | } 137 | 138 | func TestEndpoint4(t *testing.T) { 139 | awsCfg := &aws.Config{ 140 | // region value set to "" 141 | Region: aws.String(""), 142 | } 143 | result, err := getServiceEndpoint(awsCfg) 144 | assert.Equal(t, "", result, "Endpoint cannot be created") 145 | assert.NotNil(t, err) 146 | } 147 | 148 | func TestEndpoint5(t *testing.T) { 149 | e := "https://xray.us-west-1.amazonaws.com" 150 | awsCfg := &aws.Config{ 151 | Endpoint: aws.String(""), // Endpoint set to "" 152 | Region: aws.String("us-west-1"), // No endpoint 153 | } 154 | result, err := getServiceEndpoint(awsCfg) 155 | assert.Equal(t, e, result, "Endpoint created from region value") 156 | assert.Nil(t, err) 157 | } 158 | 159 | // Testing AWS China partition 160 | func TestEndpoint6(t *testing.T) { 161 | e := "https://xray.cn-northwest-1.amazonaws.com.cn" 162 | awsCfg := &aws.Config{ 163 | Endpoint: aws.String(""), 164 | Region: aws.String("cn-northwest-1"), 165 | } 166 | result, err := getServiceEndpoint(awsCfg) 167 | assert.Equal(t, e, result, "creating endpoint from region") 168 | assert.Nil(t, err) 169 | } 170 | 171 | // Testing AWS China partition 172 | func TestEndpoint7(t *testing.T) { 173 | e := "https://xray.cn-north-1.amazonaws.com.cn" 174 | awsCfg := &aws.Config{ 175 | Endpoint: aws.String(""), 176 | Region: aws.String("cn-north-1"), 177 | } 178 | result, err := getServiceEndpoint(awsCfg) 179 | assert.Equal(t, e, result, "creating endpoint from region") 180 | assert.Nil(t, err) 181 | } 182 | 183 | // Testing AWS Gov partition 184 | func TestEndpoint8(t *testing.T) { 185 | e := "https://xray.us-gov-east-1.amazonaws.com" 186 | awsCfg := &aws.Config{ 187 | Endpoint: aws.String(""), 188 | Region: aws.String("us-gov-east-1"), 189 | } 190 | result, err := getServiceEndpoint(awsCfg) 191 | assert.Equal(t, e, result, "creating endpoint from region") 192 | assert.Nil(t, err) 193 | } 194 | 195 | // Testing AWS Gov partition 196 | func TestEndpoint9(t *testing.T) { 197 | e := "https://xray.us-gov-west-1.amazonaws.com" 198 | awsCfg := &aws.Config{ 199 | Endpoint: aws.String(""), 200 | Region: aws.String("us-gov-west-1"), 201 | } 202 | result, err := getServiceEndpoint(awsCfg) 203 | assert.Equal(t, e, result, "creating endpoint from region") 204 | assert.Nil(t, err) 205 | } 206 | -------------------------------------------------------------------------------- /pkg/ringbuffer/ringbuffer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package ringbuffer 11 | 12 | import ( 13 | log "github.com/cihub/seelog" 14 | 15 | "os" 16 | 17 | "github.com/aws/aws-xray-daemon/pkg/bufferpool" 18 | "github.com/aws/aws-xray-daemon/pkg/telemetry" 19 | "github.com/aws/aws-xray-daemon/pkg/tracesegment" 20 | ) 21 | 22 | var defaultCapacity = 250 23 | 24 | // RingBuffer is used to store trace segment received on X-Ray daemon address. 25 | type RingBuffer struct { 26 | // Channel used to store trace segment received on X-Ray daemon address. 27 | Channel <-chan *tracesegment.TraceSegment 28 | c chan *tracesegment.TraceSegment 29 | 30 | // Boolean, set to true of buffer is empty 31 | Empty bool 32 | 33 | // Counter for trace segments truncated. 34 | count uint64 35 | 36 | // Reference to BufferPool. 37 | pool *bufferpool.BufferPool 38 | } 39 | 40 | // New returns new instance of RingBuffer configured with BufferPool pool. 41 | func New(bufferCount int, pool *bufferpool.BufferPool) *RingBuffer { 42 | if bufferCount == 0 { 43 | log.Error("The initial size of a queue should be larger than 0") 44 | os.Exit(1) 45 | } 46 | capacity := getChannelSize(bufferCount) 47 | channel := make(chan *tracesegment.TraceSegment, capacity) 48 | 49 | return &RingBuffer{ 50 | Channel: channel, 51 | c: channel, 52 | Empty: false, 53 | count: 0, 54 | pool: pool, 55 | } 56 | } 57 | 58 | // getChannelSize returns the size of the channel used by RingBuffer 59 | // Currently 1X times the total number of allocated buffers for the X-Ray daemon is returned. 60 | // This is proportional to number of buffers, since the segments are dropped if no new buffer can be allocated. 61 | // max(defaultCapacity, bufferCount) is returned by the function. 62 | func getChannelSize(bufferCount int) int { 63 | capacity := 1 * bufferCount 64 | if capacity < defaultCapacity { 65 | return defaultCapacity 66 | } 67 | return capacity 68 | } 69 | 70 | // Send sends trace segment s to trace segment channel. 71 | func (r *RingBuffer) Send(s *tracesegment.TraceSegment) { 72 | select { 73 | case r.c <- s: 74 | default: 75 | var segmentTruncated *tracesegment.TraceSegment 76 | select { 77 | case segmentTruncated = <-r.c: 78 | r.count++ 79 | r.pool.Return(segmentTruncated.PoolBuf) 80 | log.Warn("Segment buffer is full. Dropping oldest segment document.") 81 | telemetry.T.SegmentSpillover(1) 82 | default: 83 | log.Trace("Buffers: channel was de-queued") 84 | } 85 | r.Send(s) 86 | } 87 | } 88 | 89 | // Close closes the RingBuffer. 90 | func (r *RingBuffer) Close() { 91 | close(r.c) 92 | } 93 | 94 | // TruncatedCount returns trace segment truncated count. 95 | func (r *RingBuffer) TruncatedCount() uint64 { 96 | return r.count 97 | } 98 | -------------------------------------------------------------------------------- /pkg/ringbuffer/ringbuffer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package ringbuffer 11 | 12 | import ( 13 | "math/rand" 14 | "os" 15 | "os/exec" 16 | "strings" 17 | "testing" 18 | 19 | "github.com/aws/aws-xray-daemon/pkg/bufferpool" 20 | "github.com/aws/aws-xray-daemon/pkg/telemetry" 21 | "github.com/aws/aws-xray-daemon/pkg/tracesegment" 22 | "github.com/aws/aws-xray-daemon/pkg/util/test" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func init() { 27 | telemetry.T = telemetry.GetTestTelemetry() 28 | } 29 | 30 | func TestRingBufferNewWithZeroCapacity(t *testing.T) { 31 | bufferLimit := 100 32 | bufferSize := 256 * 1024 33 | bufferPool := bufferpool.Init(bufferLimit, bufferSize) 34 | // Only run the failing part when a specific env variable is set 35 | if os.Getenv("Test_New") == "1" { 36 | New(0, bufferPool) 37 | return 38 | } 39 | // Start the actual test in a different subprocess 40 | cmd := exec.Command(os.Args[0], "-test.run=TestRingBufferNewWithZeroCapacity") 41 | cmd.Env = append(os.Environ(), "Test_New=1") 42 | if err := cmd.Start(); err != nil { 43 | t.Fatal(err) 44 | } 45 | // Check that the program exited 46 | err := cmd.Wait() 47 | if e, ok := err.(*exec.ExitError); !ok || e.Success() { 48 | t.Fatalf("Process ran with err %v, want exit status 1", err) 49 | } 50 | } 51 | 52 | func TestRingBufferNewWithDefaultCapacity(t *testing.T) { 53 | bufferLimit := 100 54 | bufferSize := 256 * 1024 55 | bufferPool := bufferpool.Init(bufferLimit, bufferSize) 56 | 57 | randomFlag := rand.Intn(defaultCapacity - 1) 58 | ringBuffer := New(randomFlag, bufferPool) // Ring buffer initialized with less than default capacity value 59 | 60 | assert.Equal(t, defaultCapacity, cap(ringBuffer.c), "The size of buffered channel should be equal to default capacity") 61 | assert.Equal(t, defaultCapacity, cap(ringBuffer.Channel), "The size of buffered channel should be equal to default capacity") 62 | assert.Equal(t, false, ringBuffer.Empty, "The ringBuffer is not empty") 63 | assert.Equal(t, uint64(0), ringBuffer.count, "The truncated count should be 0") 64 | assert.Equal(t, bufferPool, ringBuffer.pool, "The value of bufferpool should be same with the given value") 65 | 66 | } 67 | 68 | func TestRingBufferNew(t *testing.T) { // RingBuffer size greater than defaultCapacity 69 | bufferLimit := 100 70 | bufferSize := 256 * 1024 71 | bufferPool := bufferpool.Init(bufferLimit, bufferSize) 72 | 73 | randomFlag := getTestChannelSize() 74 | ringBuffer := New(randomFlag, bufferPool) 75 | 76 | assert.Equal(t, randomFlag, cap(ringBuffer.c), "The size of buffered channel should be same with the given number") 77 | assert.Equal(t, randomFlag, cap(ringBuffer.Channel), "The size of buffered channel should be same with the given number") 78 | assert.Equal(t, false, ringBuffer.Empty, "The ringBuffer is not empty") 79 | assert.Equal(t, uint64(0), ringBuffer.count, "The truncated count should be 0") 80 | assert.Equal(t, bufferPool, ringBuffer.pool, "The value of bufferpool should be same with the given value") 81 | 82 | } 83 | 84 | func TestRingBufferCloseChannel(t *testing.T) { 85 | bufferLimit := 100 86 | bufferSize := 256 * 1024 87 | bufferPool := bufferpool.Init(bufferLimit, bufferSize) 88 | randomFlag := getTestChannelSize() 89 | ringBuffer := New(randomFlag, bufferPool) 90 | ringBuffer.Close() 91 | for i := 0; i < cap(ringBuffer.c); i++ { 92 | v, ok := <-ringBuffer.c 93 | 94 | assert.Equal(t, (*tracesegment.TraceSegment)(nil), v, "The value should be nil") 95 | assert.Equal(t, false, ok, "The value should be false if the channel is closed") 96 | } 97 | } 98 | 99 | func TestRingBufferSend(t *testing.T) { 100 | bufferLimit := 100 101 | bufferSize := 256 * 1024 102 | bufferPool := bufferpool.Init(bufferLimit, bufferSize) 103 | randomFlag := getTestChannelSize() 104 | ringBuffer := New(randomFlag, bufferPool) 105 | segment := tracesegment.GetTestTraceSegment() 106 | for i := 0; i < randomFlag; i++ { 107 | ringBuffer.Send(&segment) 108 | } 109 | for i := 0; i < cap(ringBuffer.c); i++ { 110 | v, ok := <-ringBuffer.c 111 | 112 | assert.Equal(t, &segment, v, "The value should be same with the send segment") 113 | assert.Equal(t, true, ok, "The channel is open") 114 | } 115 | } 116 | 117 | func TestRingBufferTruncatedCount(t *testing.T) { 118 | log := test.LogSetup() 119 | bufferLimit := 100 120 | bufferSize := 256 * 1024 121 | bufferPool := bufferpool.Init(bufferLimit, bufferSize) 122 | segment := tracesegment.GetTestTraceSegment() 123 | randomFlag := getTestChannelSize() 124 | ringBuffer := New(randomFlag, bufferPool) 125 | extraSegments := 100 126 | for i := 0; i < randomFlag+extraSegments; i++ { 127 | ringBuffer.Send(&segment) 128 | } 129 | num := ringBuffer.TruncatedCount() 130 | 131 | assert.Equal(t, num, uint64(extraSegments), "The truncated count should be same with the extra segments sent") 132 | for i := 0; i < extraSegments; i++ { 133 | assert.True(t, strings.Contains(log.Logs[i], "Segment buffer is full. Dropping oldest segment document.")) 134 | } 135 | } 136 | 137 | func TestRingBufferSendTruncated(t *testing.T) { 138 | log := test.LogSetup() 139 | bufferLimit := 100 140 | bufferSize := 256 * 1024 141 | bufferPool := bufferpool.Init(bufferLimit, bufferSize) 142 | randomFlag := getTestChannelSize() + 2 143 | ringBuffer := New(randomFlag, bufferPool) 144 | var segment []tracesegment.TraceSegment 145 | for i := 0; i < randomFlag; i++ { 146 | segment = append(segment, tracesegment.GetTestTraceSegment()) 147 | ringBuffer.Send(&segment[i]) 148 | } 149 | s1 := tracesegment.GetTestTraceSegment() 150 | ringBuffer.Send(&s1) 151 | 152 | assert.Equal(t, &segment[1], <-ringBuffer.c, "Truncate the first segment in the original buffered channel") 153 | assert.Equal(t, randomFlag, cap(ringBuffer.c), "The buffered channel still full after truncating") 154 | assert.True(t, strings.Contains(log.Logs[0], "Segment buffer is full. Dropping oldest segment document.")) 155 | 156 | s2 := tracesegment.GetTestTraceSegment() 157 | ringBuffer.Send(&s2) 158 | 159 | assert.Equal(t, &segment[2], <-ringBuffer.c, "Truncate the second segment that in the original buffered channel") 160 | assert.Equal(t, randomFlag, cap(ringBuffer.c), "The buffered channel still full after truncating") 161 | assert.True(t, strings.Contains(log.Logs[0], "Segment buffer is full. Dropping oldest segment document.")) 162 | } 163 | 164 | // getTestChannelSize returns a random number greater than or equal to defaultCapacity 165 | func getTestChannelSize() int { 166 | return rand.Intn(50) + defaultCapacity 167 | } 168 | -------------------------------------------------------------------------------- /pkg/socketconn/socketconn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package socketconn 11 | 12 | // SocketConn is an interface for socket connection. 13 | type SocketConn interface { 14 | // Reads a packet from the connection, copying the payload into b. It returns number of bytes copied. 15 | Read(b []byte) (int, error) 16 | 17 | // Closes the connection. 18 | Close() 19 | } 20 | -------------------------------------------------------------------------------- /pkg/socketconn/udp/udp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package udp 11 | 12 | import ( 13 | "net" 14 | "os" 15 | 16 | "github.com/aws/aws-xray-daemon/pkg/socketconn" 17 | log "github.com/cihub/seelog" 18 | ) 19 | 20 | // UDP defines UDP socket connection. 21 | type UDP struct { 22 | socket *net.UDPConn 23 | } 24 | 25 | // New returns new instance of UDP. 26 | func New(udpAddress string) socketconn.SocketConn { 27 | log.Debugf("Listening on UDP %v", udpAddress) 28 | addr, err := net.ResolveUDPAddr("udp", udpAddress) 29 | if err != nil { 30 | log.Errorf("%v", err) 31 | os.Exit(1) 32 | } 33 | sock, err := net.ListenUDP("udp", addr) 34 | if err != nil { 35 | log.Errorf("%v", err) 36 | os.Exit(1) 37 | } 38 | return UDP{ 39 | socket: sock, 40 | } 41 | } 42 | 43 | // Read returns number of bytes read from the UDP connection. 44 | func (conn UDP) Read(b []byte) (int, error) { 45 | rlen, _, err := conn.socket.ReadFromUDP(b) 46 | return rlen, err 47 | } 48 | 49 | // Close closes current UDP connection. 50 | func (conn UDP) Close() { 51 | err := conn.socket.Close() 52 | if err != nil { 53 | log.Errorf("unable to close the UDP connection: %v", err) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pkg/telemetry/telemetry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package telemetry 11 | 12 | import ( 13 | "os" 14 | "sync/atomic" 15 | "time" 16 | "unsafe" 17 | 18 | "github.com/aws/aws-xray-daemon/pkg/conn" 19 | "github.com/aws/aws-xray-daemon/pkg/util/timer" 20 | 21 | "github.com/aws/aws-sdk-go/aws" 22 | "github.com/aws/aws-sdk-go/aws/awserr" 23 | "github.com/aws/aws-sdk-go/aws/ec2metadata" 24 | "github.com/aws/aws-sdk-go/aws/session" 25 | "github.com/aws/aws-sdk-go/service/xray" 26 | log "github.com/cihub/seelog" 27 | ) 28 | 29 | const dataCutoffIntervalSecs = 60 30 | const bufferSize = 30 31 | const requestSize = 10 32 | 33 | // T is instance of Telemetry. 34 | var T *Telemetry 35 | 36 | // Telemetry is used to record X-Ray daemon health. 37 | type Telemetry struct { 38 | // Instance of XRay. 39 | client conn.XRay 40 | timer timer.Timer 41 | 42 | // Amazon Resource Name (ARN) of the AWS resource running the daemon. 43 | resourceARN string 44 | 45 | // Instance id of the EC2 instance running X-Ray daemon. 46 | instanceID string 47 | 48 | // Host name of the EC2 instance running X-Ray daemon. 49 | hostname string 50 | 51 | // Self pointer. 52 | currentRecord *xray.TelemetryRecord 53 | 54 | // Timer channel. 55 | timerChan <-chan time.Time 56 | 57 | // Boolean channel, set to true when Quit channel is set to true. 58 | Done chan bool 59 | 60 | // Boolean channel, set to true when daemon is closed, 61 | Quit chan bool 62 | 63 | // Channel of TelemetryRecord used to send to X-Ray service. 64 | recordChan chan *xray.TelemetryRecord 65 | 66 | // When segment is received, postTelemetry is set to true, 67 | // indicating send telemetry data for the received segment. 68 | postTelemetry bool 69 | } 70 | 71 | // Init instantiates a new instance of Telemetry. 72 | func Init(awsConfig *aws.Config, s *session.Session, resourceARN string, noMetadata bool) { 73 | T = newT(awsConfig, s, resourceARN, noMetadata) 74 | log.Debug("Telemetry initiated") 75 | } 76 | 77 | // EvaluateConnectionError processes error with respect to request failure status code. 78 | func EvaluateConnectionError(err error) { 79 | requestFailure, ok := err.(awserr.RequestFailure) 80 | if ok { 81 | statusCode := requestFailure.StatusCode() 82 | if statusCode >= 500 && statusCode < 600 { 83 | T.Connection5xx(1) 84 | } else if statusCode >= 400 && statusCode < 500 { 85 | T.Connection4xx(1) 86 | } else { 87 | T.ConnectionOther(1) 88 | } 89 | } else { 90 | if conn.IsTimeoutError(err) { 91 | T.ConnectionTimeout(1) 92 | } else { 93 | awsError, ok := err.(awserr.Error) 94 | if ok { 95 | if awsError.Code() == "RequestError" { 96 | T.ConnectionUnknownHost(1) 97 | } 98 | } else { 99 | T.ConnectionOther(1) 100 | } 101 | } 102 | } 103 | } 104 | 105 | // GetTestTelemetry returns an empty telemetry record. 106 | func GetTestTelemetry() *Telemetry { 107 | return &Telemetry{ 108 | currentRecord: getEmptyTelemetryRecord(), 109 | } 110 | } 111 | 112 | // SegmentReceived increments SegmentsReceivedCount for the Telemetry record. 113 | func (t *Telemetry) SegmentReceived(count int64) { 114 | atomic.AddInt64(t.currentRecord.SegmentsReceivedCount, count) 115 | // Only send telemetry data when we receive any segment or else skip any telemetry data 116 | t.postTelemetry = true 117 | } 118 | 119 | // SegmentSent increments SegmentsSentCount for the Telemetry record. 120 | func (t *Telemetry) SegmentSent(count int64) { 121 | atomic.AddInt64(t.currentRecord.SegmentsSentCount, count) 122 | } 123 | 124 | // SegmentSpillover increments SegmentsSpilloverCount for the Telemetry record. 125 | func (t *Telemetry) SegmentSpillover(count int64) { 126 | atomic.AddInt64(t.currentRecord.SegmentsSpilloverCount, count) 127 | } 128 | 129 | // SegmentRejected increments SegmentsRejectedCount for the Telemetry record. 130 | func (t *Telemetry) SegmentRejected(count int64) { 131 | atomic.AddInt64(t.currentRecord.SegmentsRejectedCount, count) 132 | } 133 | 134 | // ConnectionTimeout increments TimeoutCount for the Telemetry record. 135 | func (t *Telemetry) ConnectionTimeout(count int64) { 136 | atomic.AddInt64(t.currentRecord.BackendConnectionErrors.TimeoutCount, count) 137 | } 138 | 139 | // ConnectionRefusal increments ConnectionRefusedCount for the Telemetry record. 140 | func (t *Telemetry) ConnectionRefusal(count int64) { 141 | atomic.AddInt64(t.currentRecord.BackendConnectionErrors.ConnectionRefusedCount, count) 142 | } 143 | 144 | // Connection4xx increments HTTPCode4XXCount for the Telemetry record. 145 | func (t *Telemetry) Connection4xx(count int64) { 146 | atomic.AddInt64(t.currentRecord.BackendConnectionErrors.HTTPCode4XXCount, count) 147 | } 148 | 149 | // Connection5xx increments HTTPCode5XXCount count for the Telemetry record. 150 | func (t *Telemetry) Connection5xx(count int64) { 151 | atomic.AddInt64(t.currentRecord.BackendConnectionErrors.HTTPCode5XXCount, count) 152 | } 153 | 154 | // ConnectionUnknownHost increments unknown host BackendConnectionErrors count for the Telemetry record. 155 | func (t *Telemetry) ConnectionUnknownHost(count int64) { 156 | atomic.AddInt64(t.currentRecord.BackendConnectionErrors.UnknownHostCount, count) 157 | } 158 | 159 | // ConnectionOther increments other BackendConnectionErrors count for the Telemetry record. 160 | func (t *Telemetry) ConnectionOther(count int64) { 161 | atomic.AddInt64(t.currentRecord.BackendConnectionErrors.OtherCount, count) 162 | } 163 | 164 | func newT(awsConfig *aws.Config, s *session.Session, resourceARN string, noMetadata bool) *Telemetry { 165 | timer := &timer.Client{} 166 | hostname := "" 167 | instanceID := "" 168 | 169 | var metadataClient *ec2metadata.EC2Metadata 170 | if !noMetadata { 171 | metadataClient = ec2metadata.New(s) 172 | } 173 | 174 | hostnameEnv := os.Getenv("AWS_HOSTNAME") 175 | if hostnameEnv != "" { 176 | hostname = hostnameEnv 177 | log.Debugf("Fetch hostname %v from environment variables", hostnameEnv) 178 | } else if metadataClient != nil { 179 | hn, err := metadataClient.GetMetadata("hostname") 180 | if err != nil { 181 | log.Debugf("Get hostname metadata failed: %s", err) 182 | } else { 183 | hostname = hn 184 | log.Debugf("Using %v hostname for telemetry records", hostname) 185 | } 186 | } else { 187 | log.Debug("No hostname set for telemetry records") 188 | } 189 | 190 | instanceIDEnv := os.Getenv("AWS_INSTANCE_ID") 191 | if instanceIDEnv != "" { 192 | instanceID = instanceIDEnv 193 | log.Debugf("Fetch instance ID %v from environment variables", instanceIDEnv) 194 | } else if metadataClient != nil { 195 | instID, err := metadataClient.GetMetadata("instance-id") 196 | if err != nil { 197 | log.Errorf("Get instance id metadata failed: %s", err) 198 | } else { 199 | instanceID = instID 200 | log.Debugf("Using %v Instance Id for Telemetry records", instanceID) 201 | } 202 | } else { 203 | log.Debug("No Instance Id set for telemetry records") 204 | } 205 | record := getEmptyTelemetryRecord() 206 | t := &Telemetry{ 207 | timer: timer, 208 | resourceARN: resourceARN, 209 | instanceID: instanceID, 210 | hostname: hostname, 211 | currentRecord: record, 212 | timerChan: getDataCutoffDelay(timer), 213 | Done: make(chan bool), 214 | Quit: make(chan bool), 215 | recordChan: make(chan *xray.TelemetryRecord, bufferSize), 216 | postTelemetry: false, 217 | } 218 | telemetryClient := conn.NewXRay(awsConfig, s) 219 | t.client = telemetryClient 220 | go t.pushData() 221 | return t 222 | } 223 | 224 | func getZeroInt64() *int64 { 225 | var zero int64 226 | zero = 0 227 | return &zero 228 | } 229 | 230 | func getEmptyTelemetryRecord() *xray.TelemetryRecord { 231 | return &xray.TelemetryRecord{ 232 | SegmentsReceivedCount: getZeroInt64(), 233 | SegmentsRejectedCount: getZeroInt64(), 234 | SegmentsSentCount: getZeroInt64(), 235 | SegmentsSpilloverCount: getZeroInt64(), 236 | BackendConnectionErrors: &xray.BackendConnectionErrors{ 237 | HTTPCode4XXCount: getZeroInt64(), 238 | HTTPCode5XXCount: getZeroInt64(), 239 | ConnectionRefusedCount: getZeroInt64(), 240 | OtherCount: getZeroInt64(), 241 | TimeoutCount: getZeroInt64(), 242 | UnknownHostCount: getZeroInt64(), 243 | }, 244 | } 245 | } 246 | 247 | func (t *Telemetry) pushData() { 248 | for { 249 | quit := false 250 | select { 251 | case <-t.Quit: 252 | quit = true 253 | break 254 | case <-t.timerChan: 255 | } 256 | emptyRecord := getEmptyTelemetryRecord() 257 | recordToReport := unsafe.Pointer(emptyRecord) 258 | recordToPushPointer := unsafe.Pointer(t.currentRecord) 259 | // Rotation Logic: 260 | // Swap current record to record to report. 261 | // Record to report is set to empty record which is set to current record 262 | t.currentRecord = (*xray.TelemetryRecord)(atomic.SwapPointer(&recordToReport, 263 | recordToPushPointer)) 264 | currentTime := time.Now() 265 | record := (*xray.TelemetryRecord)(recordToReport) 266 | record.Timestamp = ¤tTime 267 | t.add(record) 268 | t.sendAll() 269 | if quit { 270 | close(t.recordChan) 271 | log.Debug("telemetry: done!") 272 | t.Done <- true 273 | break 274 | } else { 275 | t.timerChan = getDataCutoffDelay(t.timer) 276 | } 277 | } 278 | } 279 | 280 | func (t *Telemetry) add(record *xray.TelemetryRecord) { 281 | // Only send telemetry data when we receive first segment or else do not send any telemetry data. 282 | if t.postTelemetry { 283 | select { 284 | case t.recordChan <- record: 285 | default: 286 | select { 287 | case <-t.recordChan: 288 | log.Debug("Telemetry Buffers truncated") 289 | t.add(record) 290 | default: 291 | log.Debug("Telemetry Buffers dequeued") 292 | } 293 | } 294 | } else { 295 | log.Debug("Skipped telemetry data as no segments found") 296 | } 297 | } 298 | 299 | func (t *Telemetry) sendAll() { 300 | records := t.collectAllRecords() 301 | recordsNoSend, err := t.sendRecords(records) 302 | if err != nil { 303 | log.Debugf("Failed to send telemetry %v record(s). Re-queue records. %v", len(records), err) 304 | // There might be possibility that new records might be archived during re-queue records. 305 | // But as timer is set after records are send this will not happen 306 | for _, record := range recordsNoSend { 307 | t.add(record) 308 | } 309 | } 310 | } 311 | 312 | func (t *Telemetry) collectAllRecords() []*xray.TelemetryRecord { 313 | records := make([]*xray.TelemetryRecord, bufferSize) 314 | records = records[:0] 315 | var record *xray.TelemetryRecord 316 | done := false 317 | for !done { 318 | select { 319 | case record = <-t.recordChan: 320 | recordLen := len(records) 321 | if recordLen < bufferSize { 322 | records = append(records, record) 323 | } 324 | default: 325 | done = true 326 | } 327 | } 328 | return records 329 | } 330 | 331 | func (t *Telemetry) sendRecords(records []*xray.TelemetryRecord) ([]*xray.TelemetryRecord, error) { 332 | if len(records) > 0 { 333 | for i := 0; i < len(records); i = i + requestSize { 334 | endIndex := len(records) 335 | if endIndex > i+requestSize { 336 | endIndex = i + requestSize 337 | } 338 | recordsToSend := records[i:endIndex] 339 | input := xray.PutTelemetryRecordsInput{ 340 | EC2InstanceId: &t.instanceID, 341 | Hostname: &t.hostname, 342 | ResourceARN: &t.resourceARN, 343 | TelemetryRecords: recordsToSend, 344 | } 345 | _, err := t.client.PutTelemetryRecords(&input) 346 | if err != nil { 347 | EvaluateConnectionError(err) 348 | return records[i:], err 349 | } 350 | } 351 | log.Debugf("Send %v telemetry record(s)", len(records)) 352 | } 353 | return nil, nil 354 | } 355 | 356 | func getDataCutoffDelay(timer timer.Timer) <-chan time.Time { 357 | return timer.After(time.Duration(time.Second * dataCutoffIntervalSecs)) 358 | } 359 | -------------------------------------------------------------------------------- /pkg/telemetry/telemetry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package telemetry 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "strings" 16 | "testing" 17 | "github.com/aws/aws-xray-daemon/pkg/util/test" 18 | 19 | "github.com/aws/aws-sdk-go/service/xray" 20 | "github.com/stretchr/testify/assert" 21 | "github.com/stretchr/testify/mock" 22 | ) 23 | 24 | type MockXRayClient struct { 25 | mock.Mock 26 | CallNoToPutTelemetryRecords int 27 | } 28 | 29 | func (c *MockXRayClient) PutTraceSegments(input *xray.PutTraceSegmentsInput) (*xray.PutTraceSegmentsOutput, error) { 30 | return nil, nil 31 | } 32 | 33 | func (c *MockXRayClient) PutTelemetryRecords(input *xray.PutTelemetryRecordsInput) (*xray.PutTelemetryRecordsOutput, error) { 34 | c.CallNoToPutTelemetryRecords++ 35 | args := c.Called(nil) 36 | errorStr := args.String(0) 37 | var err error 38 | output := &xray.PutTelemetryRecordsOutput{} 39 | if errorStr != "" { 40 | err = errors.New(errorStr) 41 | } 42 | return output, err 43 | } 44 | 45 | func TestGetEmptyTelemetryRecord(t *testing.T) { 46 | emptyRecord := getEmptyTelemetryRecord() 47 | 48 | assert.EqualValues(t, emptyRecord.SegmentsReceivedCount, new(int64)) 49 | assert.EqualValues(t, emptyRecord.SegmentsRejectedCount, new(int64)) 50 | assert.EqualValues(t, emptyRecord.SegmentsSentCount, new(int64)) 51 | assert.EqualValues(t, emptyRecord.SegmentsSpilloverCount, new(int64)) 52 | assert.EqualValues(t, emptyRecord.BackendConnectionErrors.ConnectionRefusedCount, new(int64)) 53 | assert.EqualValues(t, emptyRecord.BackendConnectionErrors.HTTPCode4XXCount, new(int64)) 54 | assert.EqualValues(t, emptyRecord.BackendConnectionErrors.HTTPCode5XXCount, new(int64)) 55 | assert.EqualValues(t, emptyRecord.BackendConnectionErrors.OtherCount, new(int64)) 56 | assert.EqualValues(t, emptyRecord.BackendConnectionErrors.TimeoutCount, new(int64)) 57 | assert.EqualValues(t, emptyRecord.BackendConnectionErrors.UnknownHostCount, new(int64)) 58 | } 59 | 60 | func TestAddTelemetryRecord(t *testing.T) { 61 | log := test.LogSetup() 62 | timer := &test.MockTimerClient{} 63 | telemetry := &Telemetry{ 64 | client: &MockXRayClient{}, 65 | timer: timer, 66 | resourceARN: "", 67 | instanceID: "", 68 | hostname: "", 69 | currentRecord: getEmptyTelemetryRecord(), 70 | timerChan: getDataCutoffDelay(timer), 71 | Done: make(chan bool), 72 | Quit: make(chan bool), 73 | recordChan: make(chan *xray.TelemetryRecord, 1), 74 | postTelemetry: true, 75 | } 76 | 77 | telemetry.add(getEmptyTelemetryRecord()) 78 | telemetry.add(getEmptyTelemetryRecord()) 79 | 80 | assert.True(t, strings.Contains(log.Logs[0], "Telemetry Buffers truncated")) 81 | } 82 | 83 | func TestSendRecordSuccess(t *testing.T) { 84 | log := test.LogSetup() 85 | xRay := new(MockXRayClient) 86 | xRay.On("PutTelemetryRecords", nil).Return("").Once() 87 | timer := &test.MockTimerClient{} 88 | telemetry := &Telemetry{ 89 | client: xRay, 90 | timer: timer, 91 | resourceARN: "", 92 | instanceID: "", 93 | hostname: "", 94 | currentRecord: getEmptyTelemetryRecord(), 95 | timerChan: getDataCutoffDelay(timer), 96 | Done: make(chan bool), 97 | Quit: make(chan bool), 98 | recordChan: make(chan *xray.TelemetryRecord, 1), 99 | } 100 | records := make([]*xray.TelemetryRecord, 1) 101 | records[0] = getEmptyTelemetryRecord() 102 | telemetry.sendRecords(records) 103 | 104 | assert.EqualValues(t, xRay.CallNoToPutTelemetryRecords, 1) 105 | assert.True(t, strings.Contains(log.Logs[0], fmt.Sprintf("Send %v telemetry record(s)", 1))) 106 | } 107 | 108 | func TestAddRecordWithPostSegmentFalse(t *testing.T) { 109 | log := test.LogSetup() 110 | timer := &test.MockTimerClient{} 111 | telemetry := &Telemetry{ 112 | client: &MockXRayClient{}, 113 | timer: timer, 114 | resourceARN: "", 115 | instanceID: "", 116 | hostname: "", 117 | currentRecord: getEmptyTelemetryRecord(), 118 | timerChan: getDataCutoffDelay(timer), 119 | Done: make(chan bool), 120 | Quit: make(chan bool), 121 | recordChan: make(chan *xray.TelemetryRecord, 1), 122 | } 123 | 124 | telemetry.add(getEmptyTelemetryRecord()) 125 | 126 | assert.True(t, strings.Contains(log.Logs[0], "Skipped telemetry data as no segments found")) 127 | } 128 | 129 | func TestAddRecordBeforeFirstSegmentAndAfter(t *testing.T) { 130 | log := test.LogSetup() 131 | timer := &test.MockTimerClient{} 132 | telemetry := &Telemetry{ 133 | client: &MockXRayClient{}, 134 | timer: timer, 135 | resourceARN: "", 136 | instanceID: "", 137 | hostname: "", 138 | currentRecord: getEmptyTelemetryRecord(), 139 | timerChan: getDataCutoffDelay(timer), 140 | Done: make(chan bool), 141 | Quit: make(chan bool), 142 | recordChan: make(chan *xray.TelemetryRecord, 1), 143 | } 144 | 145 | // No Segment received 146 | telemetry.add(getEmptyTelemetryRecord()) 147 | 148 | assert.True(t, strings.Contains(log.Logs[0], "Skipped telemetry data as no segments found")) 149 | 150 | // Segment received 151 | telemetry.SegmentReceived(1) 152 | telemetry.add(getEmptyTelemetryRecord()) 153 | telemetry.add(getEmptyTelemetryRecord()) 154 | 155 | assert.True(t, strings.Contains(log.Logs[1], "Telemetry Buffers truncated")) 156 | } 157 | -------------------------------------------------------------------------------- /pkg/tracesegment/tracesegment.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package tracesegment 11 | 12 | import ( 13 | "bytes" 14 | "compress/zlib" 15 | log "github.com/cihub/seelog" 16 | "strings" 17 | ) 18 | 19 | // Header stores header of trace segment. 20 | type Header struct { 21 | Format string `json:"format"` 22 | Version int `json:"version"` 23 | } 24 | 25 | // IsValid validates Header. 26 | func (t Header) IsValid() bool { 27 | return strings.EqualFold(t.Format, "json") && t.Version == 1 28 | } 29 | 30 | // TraceSegment stores raw segment. 31 | type TraceSegment struct { 32 | Raw *[]byte 33 | PoolBuf *[]byte 34 | } 35 | 36 | // Deflate converts TraceSegment to bytes 37 | func (r *TraceSegment) Deflate() []byte { 38 | var b bytes.Buffer 39 | 40 | w := zlib.NewWriter(&b) 41 | rawBytes := *r.Raw 42 | 43 | _, err := w.Write(rawBytes) 44 | if err != nil { 45 | log.Errorf("%v", err) 46 | } 47 | 48 | err = w.Close() 49 | if err != nil { 50 | log.Errorf("%v", err) 51 | } 52 | 53 | return b.Bytes() 54 | } 55 | -------------------------------------------------------------------------------- /pkg/tracesegment/tracesegment_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package tracesegment 11 | 12 | import ( 13 | "bytes" 14 | "compress/zlib" 15 | "io" 16 | "testing" 17 | 18 | "github.com/stretchr/testify/assert" 19 | ) 20 | 21 | func TestDeflateWithValidInput(t *testing.T) { 22 | testSegment := GetTestTraceSegment() 23 | 24 | deflatedBytes := testSegment.Deflate() 25 | rawBytes := *testSegment.Raw 26 | 27 | assert.True(t, len(rawBytes) > len(deflatedBytes), "Deflated bytes should compress raw bytes") 28 | 29 | // Testing reverting compression using zlib 30 | deflatedBytesBuffer := bytes.NewBuffer(deflatedBytes) 31 | reader, err := zlib.NewReader(deflatedBytesBuffer) 32 | if err != nil { 33 | panic(err) 34 | } 35 | var deflatedBytesRecovered = make([]byte, 1000) 36 | n, err := reader.Read(deflatedBytesRecovered) 37 | if err != nil && err != io.EOF { 38 | panic(err) 39 | } 40 | deflatedBytesRecovered = deflatedBytesRecovered[:n] 41 | 42 | assert.Equal(t, n, len(deflatedBytesRecovered)) 43 | assert.Equal(t, len(deflatedBytesRecovered), len(rawBytes)) 44 | for index, byteVal := range rawBytes { 45 | assert.Equal(t, byteVal, deflatedBytesRecovered[index], "Difference in recovered and original bytes") 46 | } 47 | } 48 | 49 | func TestTraceSegmentHeaderIsValid(t *testing.T) { 50 | header := Header{ 51 | Format: "json", 52 | Version: 1, 53 | } 54 | 55 | valid := header.IsValid() 56 | 57 | assert.True(t, valid) 58 | } 59 | 60 | func TestTraceSegmentHeaderIsValidCaseInsensitive(t *testing.T) { 61 | header := Header{ 62 | Format: "jSoN", 63 | Version: 1, 64 | } 65 | 66 | valid := header.IsValid() 67 | 68 | assert.True(t, valid) 69 | } 70 | 71 | func TestTraceSegmentHeaderIsValidWrongVersion(t *testing.T) { 72 | header := Header{ 73 | Format: "json", 74 | Version: 2, 75 | } 76 | 77 | valid := header.IsValid() 78 | 79 | assert.False(t, valid) 80 | } 81 | 82 | func TestTraceSegmentHeaderIsValidWrongFormat(t *testing.T) { 83 | header := Header{ 84 | Format: "xml", 85 | Version: 1, 86 | } 87 | 88 | valid := header.IsValid() 89 | 90 | assert.False(t, valid) 91 | } 92 | 93 | func TestTraceSegmentHeaderIsValidWrongFormatVersion(t *testing.T) { 94 | header := Header{ 95 | Format: "xml", 96 | Version: 2, 97 | } 98 | 99 | valid := header.IsValid() 100 | 101 | assert.False(t, valid) 102 | } 103 | -------------------------------------------------------------------------------- /pkg/tracesegment/tracesegment_test_util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | // 5 | // http://aws.amazon.com/apache2.0/ 6 | // 7 | // or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | // See the License for the specific language governing permissions and limitations under the License. 9 | 10 | package tracesegment 11 | 12 | import ( 13 | "fmt" 14 | "math/rand" 15 | ) 16 | 17 | // GetTestTraceSegment returns new instance of TraceSegment used for testing. 18 | func GetTestTraceSegment() TraceSegment { 19 | traceRandomNumber := rand.Int() 20 | segmentRandomNumber := rand.Int() 21 | message := fmt.Sprintf("{\"trace_id\": \"%v\", \"id\": \"%v\", \"start_time\": 1461096053.37518, "+ 22 | "\"end_time\": 1461096053.4042, "+ 23 | "\"name\": \"hello-1.mbfzqxzcpe.us-east-1.elasticbeanstalk.com\"}", 24 | traceRandomNumber, 25 | segmentRandomNumber) 26 | buf := make([]byte, 100) 27 | messageBytes := []byte(message) 28 | 29 | segment := TraceSegment{ 30 | PoolBuf: &buf, 31 | Raw: &messageBytes, 32 | } 33 | return segment 34 | } 35 | -------------------------------------------------------------------------------- /pkg/util/test/log_writer.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | log "github.com/cihub/seelog" 5 | ) 6 | 7 | // LogWriter defines structure for log writer. 8 | type LogWriter struct { 9 | Logs []string 10 | } 11 | 12 | // Write writes p bytes to log writer. 13 | func (sw *LogWriter) Write(p []byte) (n int, err error) { 14 | sw.Logs = append(sw.Logs, string(p)) 15 | return len(p), nil 16 | } 17 | 18 | // LogSetup initializes log writer. 19 | func LogSetup() *LogWriter { 20 | writer := &LogWriter{} 21 | logger, err := log.LoggerFromWriterWithMinLevelAndFormat(writer, log.TraceLvl, "%Ns [%Level] %Msg") 22 | if err != nil { 23 | panic(err) 24 | } 25 | log.ReplaceLogger(logger) 26 | return writer 27 | } 28 | -------------------------------------------------------------------------------- /pkg/util/test/mock_timer_client.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type timer struct { 10 | start time.Time 11 | duration time.Duration 12 | repeat bool 13 | fired bool 14 | c chan time.Time 15 | } 16 | 17 | // MockTimerClient contains mock timer client. 18 | type MockTimerClient struct { 19 | sync.RWMutex 20 | 21 | current time.Time 22 | timers []*timer 23 | 24 | afterCalled uint64 25 | tickCalled uint64 26 | } 27 | 28 | func (m *MockTimerClient) newTimer(d time.Duration, repeat bool) *timer { 29 | m.RLock() 30 | t := &timer{ 31 | start: m.current, 32 | duration: d, 33 | repeat: repeat, 34 | fired: false, 35 | c: make(chan time.Time, 1), 36 | } 37 | m.RUnlock() 38 | 39 | m.Lock() 40 | defer m.Unlock() 41 | m.timers = append(m.timers, t) 42 | 43 | return t 44 | } 45 | 46 | // After is mock of time.After(). 47 | func (m *MockTimerClient) After(d time.Duration) <-chan time.Time { 48 | atomic.AddUint64(&m.afterCalled, 1) 49 | 50 | return m.newTimer(d, false).c 51 | } 52 | 53 | // Tick is mock of time.Tick(). 54 | func (m *MockTimerClient) Tick(d time.Duration) <-chan time.Time { 55 | atomic.AddUint64(&m.tickCalled, 1) 56 | 57 | return m.newTimer(d, true).c 58 | } 59 | 60 | // Advance simulates time passing and signal timers / tickers accordingly 61 | func (m *MockTimerClient) Advance(d time.Duration) { 62 | m.Lock() 63 | m.current = m.current.Add(d) 64 | m.Unlock() 65 | 66 | m.RLock() 67 | defer m.RUnlock() 68 | 69 | curr := m.current 70 | for _, t := range m.timers { 71 | if t.repeat { 72 | // for Tickers, calculate how many ticks has passed and signal accordingly 73 | for i := int64(0); i < int64(curr.Sub(t.start)/t.duration); i++ { 74 | t.c <- t.start.Add(t.duration * time.Duration(i+1)) 75 | t.start = t.start.Add(t.duration) 76 | } 77 | } else { 78 | // for Afters (one-off), signal once 79 | if !t.fired && (curr.Sub(t.start) >= t.duration) { 80 | t.c <- t.start.Add(t.duration) 81 | t.fired = true 82 | } 83 | } 84 | } 85 | } 86 | 87 | // AfterCalledTimes calculates number of times after is called. 88 | func (m *MockTimerClient) AfterCalledTimes() uint64 { 89 | return atomic.LoadUint64(&m.afterCalled) 90 | } 91 | 92 | // TickCalledTimes calculates number of times tick is called. 93 | func (m *MockTimerClient) TickCalledTimes() uint64 { 94 | return atomic.LoadUint64(&m.tickCalled) 95 | } 96 | -------------------------------------------------------------------------------- /pkg/util/test/mock_timer_client_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type EmptyStruct struct { 12 | } 13 | 14 | func ChannelHasData(c chan EmptyStruct) bool { 15 | var ok bool 16 | select { 17 | case <-c: 18 | ok = true 19 | default: 20 | ok = false 21 | 22 | } 23 | return ok 24 | } 25 | 26 | // This function is used so that test cases will not freeze if chan is not responsive 27 | func TryToGetValue(ch chan EmptyStruct) *EmptyStruct { 28 | timeout := make(chan bool, 1) 29 | go func() { 30 | time.Sleep(100 * time.Millisecond) 31 | timeout <- true 32 | }() 33 | select { 34 | case v := <-ch: 35 | return &v 36 | case <-timeout: 37 | return nil 38 | } 39 | } 40 | 41 | func TickTestHelper(tickDuration int64, t *testing.T) { 42 | timer := &MockTimerClient{current: time.Unix(35534432431, 0)} 43 | tickChan := make(chan EmptyStruct, 1) 44 | tickFunc := func() { 45 | // Go routine started 46 | tickChan <- EmptyStruct{} 47 | t := timer.Tick(time.Duration(tickDuration)) 48 | for { 49 | <-t 50 | tickChan <- EmptyStruct{} 51 | } 52 | } 53 | 54 | go tickFunc() 55 | 56 | // Go routine to monitor tick started 57 | <-tickChan 58 | testCasesTicksToTrigger := []int{1, 2, 1000} 59 | var durationIncremented int64 60 | for _, ticksToTrigger := range testCasesTicksToTrigger { 61 | for i := 0; i < ticksToTrigger; i++ { 62 | var ok bool 63 | ok = ChannelHasData(tickChan) 64 | assert.False(t, ok) 65 | initialIncrement := tickDuration / 2 66 | // Not enough to trigger tick 67 | timer.Advance(time.Duration(initialIncrement)) 68 | durationIncremented += initialIncrement 69 | ok = ChannelHasData(tickChan) 70 | assert.False(t, ok) 71 | // tick triggered 72 | timer.Advance(time.Duration(tickDuration)) 73 | durationIncremented += tickDuration 74 | val := TryToGetValue(tickChan) 75 | assert.NotNil(t, 76 | val, 77 | fmt.Sprintf("Expected value passed thru the channel. Tick Duration: %v, Tick Trigger Iteration: %v, Ticket To Trigger: %v Current Clock Time: %v", 78 | tickDuration, 79 | i, 80 | ticksToTrigger, 81 | timer.current)) 82 | 83 | // Adding 4th of the duration to trigger 84 | durationForth := tickDuration / 4 85 | timer.Advance(time.Duration(durationForth)) 86 | durationIncremented += durationForth 87 | ok = ChannelHasData(tickChan) 88 | assert.False(t, ok) 89 | 90 | // Leave the duration with exact divisor so that next loop can assume 91 | // duration increment is zero 92 | finalIncrement := tickDuration*2 - durationIncremented 93 | // tick triggered 94 | timer.Advance(time.Duration(finalIncrement)) 95 | val = TryToGetValue(tickChan) 96 | assert.NotNil(t, val) 97 | durationIncremented = 0 98 | } 99 | } 100 | 101 | assert.EqualValues(t, 1, timer.TickCalledTimes()) 102 | } 103 | 104 | func TestTickDuration454(t *testing.T) { 105 | var tickDuration int64 106 | tickDuration = 454 107 | TickTestHelper(tickDuration, t) 108 | } 109 | 110 | func TestAfter(t *testing.T) { 111 | var afterDuration int64 112 | afterDuration = 10 113 | timer := MockTimerClient{current: time.Unix(2153567564, 0)} 114 | afterChan := make(chan EmptyStruct, 1) 115 | tickFunc := func() { 116 | // Go routine started 117 | afterChan <- EmptyStruct{} 118 | t := timer.After(time.Duration(afterDuration)) 119 | for { 120 | <-t 121 | afterChan <- EmptyStruct{} 122 | } 123 | } 124 | 125 | go tickFunc() 126 | 127 | // Go routine started to monitor after messages 128 | <-afterChan 129 | var ok bool 130 | ok = ChannelHasData(afterChan) 131 | assert.False(t, ok) 132 | initialIncrement := afterDuration / 2 133 | // Not enough to trigger after 134 | timer.Advance(time.Duration(initialIncrement)) 135 | ok = ChannelHasData(afterChan) 136 | assert.False(t, ok) 137 | // after triggered 138 | timer.Advance(time.Duration(afterDuration)) 139 | val := TryToGetValue(afterChan) 140 | assert.NotNil(t, val, fmt.Sprintf("Expected value passed thru the channel. After Duration: %v, Current Clock Time: %v", afterDuration, timer.current)) 141 | 142 | // After should trigger only once compared to tick 143 | timer.Advance(time.Duration(afterDuration)) 144 | ok = ChannelHasData(afterChan) 145 | assert.False(t, ok) 146 | 147 | assert.EqualValues(t, 1, timer.AfterCalledTimes()) 148 | } 149 | 150 | func TestAfterTickTogether(t *testing.T) { 151 | var tickDuration int64 152 | tickDuration = 10 153 | afterDuration := tickDuration * 2 154 | timer := MockTimerClient{current: time.Unix(23082153551, 0)} 155 | tickChan := make(chan EmptyStruct, 1) 156 | afterChan := make(chan EmptyStruct, 1) 157 | tickFunc := func() { 158 | // Go routine started 159 | tick := timer.Tick(time.Duration(tickDuration)) 160 | tickChan <- EmptyStruct{} 161 | for { 162 | select { 163 | case <-tick: 164 | tickChan <- EmptyStruct{} 165 | } 166 | } 167 | } 168 | afterFunc := func() { 169 | // Go routine started 170 | after := timer.After(time.Duration(afterDuration)) 171 | afterChan <- EmptyStruct{} 172 | for { 173 | select { 174 | case <-after: 175 | afterChan <- EmptyStruct{} 176 | 177 | } 178 | } 179 | } 180 | 181 | go tickFunc() 182 | go afterFunc() 183 | 184 | // Go routine started to monitor tick and after events 185 | <-tickChan 186 | <-afterChan 187 | testCasesTicksToTrigger := []int{1, 2, 100} 188 | var durationIncremented int64 189 | for triggerIndex, ticksToTrigger := range testCasesTicksToTrigger { 190 | for i := 0; i < ticksToTrigger; i++ { 191 | var ok bool 192 | ok = ChannelHasData(tickChan) 193 | assert.False(t, ok) 194 | ok = ChannelHasData(afterChan) 195 | assert.False(t, ok) 196 | initialIncrement := tickDuration / 2 197 | // Not enough to trigger tick 198 | timer.Advance(time.Duration(initialIncrement)) 199 | durationIncremented += initialIncrement 200 | ok = ChannelHasData(tickChan) 201 | assert.False(t, ok) 202 | ok = ChannelHasData(afterChan) 203 | assert.False(t, ok) 204 | // tick triggered 205 | timer.Advance(time.Duration(tickDuration)) 206 | durationIncremented += tickDuration 207 | val := TryToGetValue(tickChan) 208 | assert.NotNil(t, val) 209 | ok = ChannelHasData(afterChan) 210 | assert.False(t, ok) 211 | 212 | // Adding 4th of the duration to trigger 213 | durationForth := tickDuration / 4 214 | timer.Advance(time.Duration(durationForth)) 215 | durationIncremented += durationForth 216 | ok = ChannelHasData(tickChan) 217 | assert.False(t, ok) 218 | ok = ChannelHasData(afterChan) 219 | assert.False(t, ok) 220 | 221 | // Leave the duration with exact divisor so that next loop can assume 222 | // duration increment is zero 223 | finalIncrement := tickDuration*2 - durationIncremented 224 | // tick triggered 225 | timer.Advance(time.Duration(finalIncrement)) 226 | // After will only trigger for first iteration as it only trigger once 227 | if (triggerIndex == 0) && (i == 0) { 228 | val = TryToGetValue(afterChan) 229 | assert.NotNil(t, val) 230 | } else { 231 | ok = ChannelHasData(afterChan) 232 | assert.False(t, ok) 233 | } 234 | val = TryToGetValue(tickChan) 235 | assert.NotNil(t, val) 236 | 237 | durationIncremented = 0 238 | } 239 | } 240 | 241 | assert.EqualValues(t, 1, timer.TickCalledTimes()) 242 | assert.EqualValues(t, 1, timer.AfterCalledTimes()) 243 | } 244 | -------------------------------------------------------------------------------- /pkg/util/timer/timer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import "time" 4 | 5 | // Timer interface 6 | type Timer interface { 7 | Tick(d time.Duration) <-chan time.Time 8 | After(d time.Duration) <-chan time.Time 9 | } 10 | 11 | // Client is an empty timer client. 12 | type Client struct{} 13 | 14 | // Tick is wrapper to time.Tick(). 15 | func (t *Client) Tick(d time.Duration) <-chan time.Time { 16 | return time.Tick(d) 17 | } 18 | 19 | // After is wrapper to time.After(). 20 | func (t *Client) After(d time.Duration) <-chan time.Time { 21 | return time.After(d) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | 6 | log "github.com/cihub/seelog" 7 | ) 8 | 9 | // SplitHeaderBody separates header and body of buf using provided separator sep, and stores in returnByte. 10 | func SplitHeaderBody(buf, sep *[]byte, returnByte *[][]byte) [][]byte { 11 | if buf == nil { 12 | log.Error("Buf to split passed nil") 13 | return nil 14 | } 15 | if sep == nil { 16 | log.Error("Separator used to split passed nil") 17 | return nil 18 | } 19 | if returnByte == nil { 20 | log.Error("Return Buf to be used to store split passed nil") 21 | return nil 22 | } 23 | 24 | separator := *sep 25 | bufVal := *buf 26 | lenSeparator := len(separator) 27 | var header, body []byte 28 | header = *buf 29 | for i := 0; i < len(bufVal); i++ { 30 | if bytes.Equal(bufVal[i:i+lenSeparator], separator) { 31 | header = bufVal[0:i] 32 | body = bufVal[i+lenSeparator:] 33 | break 34 | } 35 | if i == len(bufVal)-1 { 36 | log.Warnf("Missing header: %s", header) 37 | } 38 | } 39 | returnByteVal := *returnByte 40 | return append(returnByteVal[:0], header, body) 41 | } 42 | 43 | // GetMinIntValue returns minimum between a and b. 44 | func GetMinIntValue(a, b int) int { 45 | if a < b { 46 | return a 47 | } 48 | return b 49 | } 50 | 51 | // Bool return pointer to input parameter 52 | func Bool(b bool) *bool { 53 | return &b 54 | } 55 | -------------------------------------------------------------------------------- /pkg/util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "github.com/aws/aws-xray-daemon/pkg/util/test" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSplitHeaderBodyWithSeparatorExists(t *testing.T) { 12 | str := "Header\nBody" 13 | separator := "\n" 14 | buf := []byte(str) 15 | separatorArray := []byte(separator) 16 | result := make([][]byte, 2) 17 | 18 | returnResult := SplitHeaderBody(&buf, &separatorArray, &result) 19 | 20 | assert.EqualValues(t, len(result), 2) 21 | assert.EqualValues(t, string(result[0]), "Header") 22 | assert.EqualValues(t, string(result[1]), "Body") 23 | assert.EqualValues(t, string(returnResult[0]), "Header") 24 | assert.EqualValues(t, string(returnResult[1]), "Body") 25 | assert.EqualValues(t, string(buf), str) 26 | assert.EqualValues(t, string(separatorArray), separator) 27 | } 28 | 29 | func TestSplitHeaderBodyWithSeparatorDoesNotExist(t *testing.T) { 30 | str := "Header" 31 | separator := "\n" 32 | buf := []byte(str) 33 | separatorArray := []byte(separator) 34 | result := make([][]byte, 2) 35 | 36 | returnResult := SplitHeaderBody(&buf, &separatorArray, &result) 37 | 38 | assert.EqualValues(t, len(result), 2) 39 | assert.EqualValues(t, string(result[0]), "Header") 40 | assert.EqualValues(t, string(result[1]), "") 41 | assert.EqualValues(t, string(returnResult[0]), "Header") 42 | assert.EqualValues(t, string(returnResult[1]), "") 43 | assert.EqualValues(t, string(buf), str) 44 | assert.EqualValues(t, string(separatorArray), separator) 45 | } 46 | 47 | func TestSplitHeaderBodyNilBuf(t *testing.T) { 48 | log := test.LogSetup() 49 | separator := "\n" 50 | separatorArray := []byte(separator) 51 | result := make([][]byte, 2) 52 | SplitHeaderBody(nil, &separatorArray, &result) 53 | 54 | assert.True(t, strings.Contains(log.Logs[0], "Buf to split passed nil")) 55 | } 56 | 57 | func TestSplitHeaderBodyNilSeparator(t *testing.T) { 58 | log := test.LogSetup() 59 | str := "Test String" 60 | buf := []byte(str) 61 | result := make([][]byte, 2) 62 | 63 | SplitHeaderBody(&buf, nil, &result) 64 | 65 | assert.True(t, strings.Contains(log.Logs[0], "Separator used to split passed nil")) 66 | } 67 | 68 | func TestSplitHeaderBodyNilResult(t *testing.T) { 69 | log := test.LogSetup() 70 | str := "Test String" 71 | buf := []byte(str) 72 | separator := "\n" 73 | separatorArray := []byte(separator) 74 | SplitHeaderBody(&buf, &separatorArray, nil) 75 | 76 | assert.True(t, strings.Contains(log.Logs[0], "Return Buf to be used to store split passed nil")) 77 | } 78 | 79 | func TestGetMinIntValue(t *testing.T) { 80 | assert.Equal(t, GetMinIntValue(1, 1), 1, "Return value should be 1") 81 | assert.Equal(t, GetMinIntValue(0, 1), 0, "Return value should be 0") 82 | assert.Equal(t, GetMinIntValue(1, 0), 0, "Return value should be 0") 83 | } 84 | -------------------------------------------------------------------------------- /testing/README.md: -------------------------------------------------------------------------------- 1 | ## Testing X-Ray Daemon 2 | 3 | This directory contains the resources and logic for testing the functionality of X-Ray Daemon for various test cases. The testcases leverage Terraform with variable configurations to launch X-Ray Daemon on AWS EC2 instances and send a trace through it. 4 | 5 | Current features of this testing suite are: 6 | 1. Testing the `.zip`, `.deb`, and `.rpm` binaries for x86_64 architecture of Linux platform. 7 | 2. Testing the `.zip` binary for x86_64 architecture of Linux platform in China and US Gov AWS partitions. 8 | -------------------------------------------------------------------------------- /testing/generate_trace_data.sh: -------------------------------------------------------------------------------- 1 | START_TIME=$(date +%s) 2 | HEX_TIME=$(printf '%x\n' $START_TIME) 3 | GUID=$(dd if=/dev/random bs=12 count=1 2>/dev/null | od -An -tx1 | tr -d ' \t\n') 4 | TRACE_ID="1-$HEX_TIME-$GUID" 5 | SEGMENT_ID=$(dd if=/dev/random bs=8 count=1 2>/dev/null | od -An -tx1 | tr -d ' \t\n') 6 | SEGMENT_DOC="{\"trace_id\": \"$TRACE_ID\", \"id\": \"$SEGMENT_ID\", \"start_time\": $START_TIME, \"in_progress\": true, \"name\": \"XRay-Daemon-Test\"}" 7 | HEADER='{"format": "json", "version": 1}' 8 | TRACE_DATA="$HEADER\n$SEGMENT_DOC" 9 | echo "$HEADER" > trace_document.txt 10 | echo "$SEGMENT_DOC" >> trace_document.txt 11 | 12 | echo "TRACE_ID=$TRACE_ID" >> $GITHUB_ENV 13 | -------------------------------------------------------------------------------- /testing/terraform/amis.tf: -------------------------------------------------------------------------------- 1 | variable "ami_family" { 2 | default = { 3 | debian = { 4 | login_user = "ubuntu" 5 | instance_type = "t2.micro" 6 | connection_type = "ssh" 7 | wait_cloud_init = "for i in {1..300}; do [ ! -f /var/lib/cloud/instance/boot-finished ] && echo 'Waiting for cloud-init...'$i && sleep 1 || break; done" 8 | } 9 | linux = { 10 | login_user = "ec2-user" 11 | instance_type = "t2.micro" 12 | connection_type = "ssh" 13 | wait_cloud_init = "for i in {1..300}; do [ ! -f /var/lib/cloud/instance/boot-finished ] && echo 'Waiting for cloud-init...'$i && sleep 1 || break; done" 14 | } 15 | } 16 | } 17 | 18 | variable "amis" { 19 | default = { 20 | ubuntu18 = { 21 | os_family = "ubuntu" 22 | ami_search_pattern = "ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server*" 23 | ami_owner = "099720109477" 24 | ami_id = "ami-02da34c96f69d525c" 25 | ami_product_code = [] 26 | family = "debian" 27 | arch = "x86_64" 28 | login_user = "ubuntu" 29 | } 30 | amazonlinux2 = { 31 | os_family = "amazon_linux" 32 | ami_search_pattern = "amzn2-ami-hvm-2.0.????????.?-x86_64-gp2" 33 | ami_owner = "amazon" 34 | ami_id = "ami-0d08ef957f0e4722b" 35 | ami_product_code = [] 36 | family = "linux" 37 | arch = "x86_64" 38 | login_user = "ec2-user" 39 | } 40 | redhat8 = { 41 | os_family = "redhat" 42 | ami_search_pattern = "RHEL-8.6.0_HVM*" 43 | ami_owner = "309956199498" 44 | ami_id = "ami-087c2c50437d0b80d" 45 | ami_product_code = [] 46 | family = "linux" 47 | arch = "x86_64" 48 | } 49 | } 50 | } 51 | 52 | data "aws_ami" "ec2_ami" { 53 | most_recent = true 54 | 55 | filter { 56 | name = "name" 57 | values = [var.amis[var.testing_ami]["ami_search_pattern"]] 58 | } 59 | 60 | filter { 61 | name = "virtualization-type" 62 | values = ["hvm"] 63 | } 64 | 65 | filter { 66 | name = "architecture" 67 | values = [var.amis[var.testing_ami]["arch"]] 68 | } 69 | 70 | owners = [var.amis[var.testing_ami]["ami_owner"]] 71 | } -------------------------------------------------------------------------------- /testing/terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 4.16" 6 | } 7 | } 8 | 9 | required_version = ">=1.2.0" 10 | } 11 | 12 | provider "aws" { 13 | region = var.aws_region 14 | } 15 | 16 | resource "random_id" "testing_id" { 17 | byte_length = 8 18 | } 19 | 20 | ######################################### 21 | ## Create a SSH key pair for EC2 instance. 22 | ## Or get an existing one from a S3 bucket 23 | resource "tls_private_key" "ssh_key" { 24 | count = var.ssh_key_name == "" ? 1 : 0 25 | algorithm = "RSA" 26 | rsa_bits = 4096 27 | } 28 | 29 | resource "aws_key_pair" "aws_ssh_key" { 30 | count = var.ssh_key_name == "" ? 1 : 0 31 | key_name = "keypair-${random_id.testing_id.hex}" 32 | public_key = tls_private_key.ssh_key[0].public_key_openssh 33 | } 34 | 35 | data "aws_s3_bucket_object" "ssh_private_key" { 36 | count = var.ssh_key_name != "" ? 1 : 0 37 | bucket = var.sshkey_s3_bucket 38 | key = var.sshkey_s3_private_key 39 | } 40 | 41 | locals { 42 | ssh_key_name = var.ssh_key_name != "" ? var.ssh_key_name : aws_key_pair.aws_ssh_key[0].key_name 43 | private_key_content = var.ssh_key_name != "" ? data.aws_s3_bucket_object.ssh_private_key[0].body : tls_private_key.ssh_key[0].private_key_pem 44 | } 45 | 46 | # save the private key locally in debug mode. 47 | resource "local_file" "private_key" { 48 | count = var.debug ? 1 : 0 49 | filename = "private_key.pem" 50 | content = local.private_key_content 51 | } 52 | ######################################### 53 | 54 | ######################################### 55 | ## Provision EC2 instances and run X-Ray Daemon 56 | 57 | locals { 58 | selected_ami = var.amis[var.testing_ami] 59 | ami_family = var.ami_family[local.selected_ami["family"]] 60 | ami_id = var.amis[var.testing_ami]["ami_id"] 61 | instance_type = lookup(local.selected_ami, "instance_type", local.ami_family["instance_type"]) 62 | login_user = lookup(local.selected_ami, "login_user", local.ami_family["login_user"]) 63 | connection_type = local.ami_family["connection_type"] 64 | ec2_instance_profile = var.ec2_instance_profile 65 | } 66 | 67 | resource "aws_security_group" "ec2_sec_group" { 68 | name_prefix = "daemon-test-sg-" 69 | description = "Allow HTTP and SSH traffic via Terraform" 70 | 71 | ingress { 72 | from_port = 22 73 | to_port = 22 74 | protocol = "tcp" 75 | cidr_blocks = ["0.0.0.0/0"] 76 | } 77 | 78 | ingress { 79 | from_port = 80 80 | to_port = 80 81 | protocol = "tcp" 82 | cidr_blocks = ["0.0.0.0/0"] 83 | } 84 | 85 | egress { 86 | from_port = 0 87 | to_port = 0 88 | protocol = "-1" 89 | cidr_blocks = ["0.0.0.0/0"] 90 | } 91 | } 92 | 93 | resource "aws_instance" "xray_daemon" { 94 | ami = data.aws_ami.ec2_ami.id 95 | instance_type = local.instance_type 96 | key_name = local.ssh_key_name 97 | iam_instance_profile = local.ec2_instance_profile 98 | vpc_security_group_ids = [aws_security_group.ec2_sec_group.id] 99 | tags = { 100 | Name = "XRayDaemon" 101 | } 102 | metadata_options { 103 | http_endpoint = "enabled" 104 | http_tokens = "required" 105 | http_put_response_hop_limit = 2 106 | } 107 | } 108 | 109 | resource "null_resource" "wait_for_instance_ready" { 110 | depends_on = [ 111 | aws_instance.xray_daemon 112 | ] 113 | provisioner "remote-exec" { 114 | inline = [ 115 | local.ami_family["wait_cloud_init"] 116 | ] 117 | connection { 118 | type = local.connection_type 119 | user = local.login_user 120 | private_key = local.private_key_content 121 | host = aws_instance.xray_daemon.public_ip 122 | } 123 | } 124 | } 125 | 126 | resource "null_resource" "copy_daemon_binary_to_instance" { 127 | depends_on = [ 128 | null_resource.wait_for_instance_ready 129 | ] 130 | provisioner "file" { 131 | source = var.daemon_package_local_path 132 | destination = var.daemon_file_name 133 | 134 | connection { 135 | type = local.connection_type 136 | user = local.login_user 137 | private_key = local.private_key_content 138 | host = aws_instance.xray_daemon.public_ip 139 | } 140 | } 141 | } 142 | 143 | resource "null_resource" "install_daemon" { 144 | depends_on = [ 145 | null_resource.copy_daemon_binary_to_instance 146 | ] 147 | 148 | provisioner "remote-exec" { 149 | inline = [ 150 | var.daemon_install_command 151 | ] 152 | connection { 153 | type = local.connection_type 154 | user = local.login_user 155 | private_key = local.private_key_content 156 | host = aws_instance.xray_daemon.public_ip 157 | } 158 | } 159 | } 160 | 161 | resource "null_resource" "start_daemon" { 162 | depends_on = [ 163 | null_resource.install_daemon 164 | ] 165 | 166 | provisioner "remote-exec" { 167 | inline = [ 168 | var.daemon_start_command, 169 | "echo sleeping for 10 seconds", 170 | "for i in {1..10}; do echo 'Sleeping...'$i && sleep 1; done" 171 | ] 172 | connection { 173 | type = local.connection_type 174 | user = local.login_user 175 | private_key = local.private_key_content 176 | host = aws_instance.xray_daemon.public_ip 177 | } 178 | } 179 | } 180 | 181 | resource "null_resource" "copy_trace_data_to_remote" { 182 | depends_on = [ 183 | null_resource.start_daemon 184 | ] 185 | 186 | provisioner "file" { 187 | source = var.trace_doc_file_path 188 | destination = "/home/${local.login_user}/${var.trace_doc_file_name}" 189 | 190 | connection { 191 | type = local.connection_type 192 | user = local.login_user 193 | private_key = local.private_key_content 194 | host = aws_instance.xray_daemon.public_ip 195 | } 196 | } 197 | } 198 | 199 | resource "null_resource" "send_trace_data_to_daemon" { 200 | depends_on = [ 201 | null_resource.copy_trace_data_to_remote 202 | ] 203 | 204 | provisioner "remote-exec" { 205 | inline = [ 206 | "#!/bin/bash", 207 | "cat ${var.trace_doc_file_name} > /dev/udp/127.0.0.1/2000" 208 | ] 209 | connection { 210 | type = local.connection_type 211 | user = local.login_user 212 | private_key = local.private_key_content 213 | host = aws_instance.xray_daemon.public_ip 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /testing/terraform/testcases/linux_deb.tfvars: -------------------------------------------------------------------------------- 1 | testing_ami = "ubuntu18" 2 | daemon_file_name = "aws-xray-daemon-3.x.deb" 3 | daemon_install_command = "sudo dpkg -i aws-xray-daemon-3.x.deb" 4 | daemon_start_command = "sudo /usr/bin/xray -a start" 5 | daemon_package_local_path = "../../distributions/aws-xray-daemon-3.x.deb" -------------------------------------------------------------------------------- /testing/terraform/testcases/linux_rpm.tfvars: -------------------------------------------------------------------------------- 1 | testing_ami = "redhat8" 2 | daemon_file_name = "aws-xray-daemon-3.x.rpm" 3 | daemon_install_command = "sudo rpm -Uvh aws-xray-daemon-3.x.rpm" 4 | daemon_start_command = "sudo /usr/bin/xray -a start" 5 | daemon_package_local_path = "../../distributions/aws-xray-daemon-3.x.rpm" -------------------------------------------------------------------------------- /testing/terraform/testcases/linux_zip.tfvars: -------------------------------------------------------------------------------- 1 | testing_ami = "amazonlinux2" 2 | daemon_file_name = "aws-xray-daemon-linux-3.x.zip" 3 | daemon_install_command = "unzip aws-xray-daemon-linux-3.x.zip -d ./xray_daemon" 4 | daemon_start_command = "nohup sudo ./xray_daemon/xray --log-level debug --log-file ./xray_daemon/xray_log &" 5 | daemon_package_local_path = "../../distributions/aws-xray-daemon-linux-3.x.zip" -------------------------------------------------------------------------------- /testing/terraform/testcases/linux_zip_cn.tfvars: -------------------------------------------------------------------------------- 1 | testing_ami = "amazonlinux2" 2 | daemon_file_name = "aws-xray-daemon-linux-3.x.zip" 3 | daemon_install_command = "unzip aws-xray-daemon-linux-3.x.zip -d ./xray_daemon" 4 | daemon_start_command = "nohup sudo ./xray_daemon/xray --log-level debug --log-file ./xray_daemon/xray_log &" 5 | daemon_package_local_path = "../../distributions/aws-xray-daemon-linux-3.x.zip" -------------------------------------------------------------------------------- /testing/terraform/testcases/linux_zip_us_gov.tfvars: -------------------------------------------------------------------------------- 1 | testing_ami = "amazonlinux2" 2 | daemon_file_name = "aws-xray-daemon-linux-3.x.zip" 3 | daemon_install_command = "unzip aws-xray-daemon-linux-3.x.zip -d ./xray_daemon" 4 | daemon_start_command = "nohup sudo ./xray_daemon/xray --log-level debug --log-file ./xray_daemon/xray_log &" 5 | daemon_package_local_path = "../../distributions/aws-xray-daemon-linux-3.x.zip" -------------------------------------------------------------------------------- /testing/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "testing_ami" { 2 | default = "amazonlinux2" 3 | } 4 | 5 | variable "daemon_file_name" { 6 | default = "" 7 | } 8 | 9 | variable "daemon_install_command" { 10 | default = "" 11 | } 12 | 13 | variable "daemon_start_command" { 14 | default = "" 15 | } 16 | 17 | variable "daemon_package_local_path" { 18 | default = "" 19 | } 20 | 21 | variable "ec2_instance_profile" { 22 | default = "XRayDaemonTestingRole" 23 | } 24 | 25 | variable "ssh_key_name" { 26 | default = "" 27 | } 28 | 29 | variable "sshkey_s3_bucket" { 30 | default = "" 31 | } 32 | 33 | variable "sshkey_s3_private_key" { 34 | default = "" 35 | } 36 | 37 | variable "aws_region" { 38 | default = "us-west-2" 39 | } 40 | 41 | variable "debug" { 42 | type = bool 43 | default = false 44 | } 45 | 46 | variable "trace_doc_file_path" { 47 | default = "../../trace_document.txt" 48 | } 49 | 50 | variable "trace_doc_file_name" { 51 | default = "trace_document.txt" 52 | } --------------------------------------------------------------------------------