├── .circleci
├── Dockerfile-openjdk7
└── config.yml
├── .github
├── CODEOWNERS
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── .gitlab-ci.yml
├── CHANGELOG.md
├── DEPS.md
├── LICENSE
├── README.md
├── THIRDPARTY.md
├── gen_notice.py
├── pom.xml
├── settings.xml
├── src
├── main
│ ├── java
│ │ └── com
│ │ │ └── timgroup
│ │ │ └── statsd
│ │ │ ├── AlphaNumericMessage.java
│ │ │ ├── BufferPool.java
│ │ │ ├── CgroupReader.java
│ │ │ ├── ClientChannel.java
│ │ │ ├── DatagramClientChannel.java
│ │ │ ├── DirectStatsDClient.java
│ │ │ ├── Event.java
│ │ │ ├── InvalidMessageException.java
│ │ │ ├── MapUtils.java
│ │ │ ├── Message.java
│ │ │ ├── NamedPipeClientChannel.java
│ │ │ ├── NamedPipeSocketAddress.java
│ │ │ ├── NoOpDirectStatsDClient.java
│ │ │ ├── NoOpStatsDClient.java
│ │ │ ├── NonBlockingDirectStatsDClient.java
│ │ │ ├── NonBlockingStatsDClient.java
│ │ │ ├── NonBlockingStatsDClientBuilder.java
│ │ │ ├── NumericMessage.java
│ │ │ ├── ServiceCheck.java
│ │ │ ├── StatsDAggregator.java
│ │ │ ├── StatsDBlockingProcessor.java
│ │ │ ├── StatsDClient.java
│ │ │ ├── StatsDClientErrorHandler.java
│ │ │ ├── StatsDClientException.java
│ │ │ ├── StatsDNonBlockingProcessor.java
│ │ │ ├── StatsDProcessor.java
│ │ │ ├── StatsDSender.java
│ │ │ ├── StatsDThreadFactory.java
│ │ │ ├── Telemetry.java
│ │ │ ├── UnixDatagramClientChannel.java
│ │ │ ├── UnixSocketAddressWithTransport.java
│ │ │ ├── UnixStreamClientChannel.java
│ │ │ └── Utf8.java
│ └── resources
│ │ ├── META-INF
│ │ └── NOTICE
│ │ └── dogstatsd
│ │ └── version.properties
└── test
│ └── java
│ └── com
│ └── timgroup
│ └── statsd
│ ├── BuilderAddressTest.java
│ ├── CgroupReaderTest.java
│ ├── DummyLowMemStatsDServer.java
│ ├── DummyStatsDServer.java
│ ├── EventTest.java
│ ├── NamedPipeDummyStatsDServer.java
│ ├── NamedPipeTest.java
│ ├── NonBlockingDirectStatsDClientTest.java
│ ├── NonBlockingStatsDClientMaxPerfTest.java
│ ├── NonBlockingStatsDClientPerfTest.java
│ ├── NonBlockingStatsDClientTest.java
│ ├── RecordingErrorHandler.java
│ ├── StatsDAggregatorTest.java
│ ├── StatsDTestMessage.java
│ ├── TelemetryTest.java
│ ├── TestHelpers.java
│ ├── UDPDummyStatsDServer.java
│ ├── UnixDatagramSocketDummyStatsDServer.java
│ ├── UnixSocketTest.java
│ ├── UnixStreamSocketDummyStatsDServer.java
│ ├── UnixStreamSocketTest.java
│ └── Utf8Test.java
├── style.xml
└── vendor
├── buildlib
├── hamcrest-core-1.3.0RC2.jar
├── hamcrest-library-1.3.0RC2.jar
└── junit-dep-4.10.jar
└── src
└── junit-dep-4.10-sources.jar
/.circleci/Dockerfile-openjdk7:
--------------------------------------------------------------------------------
1 | FROM openjdk:7-jdk
2 |
3 | RUN apt update && apt install -y maven
4 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | win: circleci/windows@2.4.1
5 |
6 | commands:
7 | create_custom_cache_lock:
8 | description: "Create custom cache lock for java version."
9 | parameters:
10 | filename:
11 | type: string
12 | steps:
13 | - run:
14 | name: Grab java version and dump to file
15 | command: java -version > << parameters.filename >>
16 |
17 | default_steps: &default_steps
18 | steps:
19 | - checkout
20 |
21 | - run: |
22 | mvn clean install $MVN_EXTRA_OPTS
23 |
24 | jobs:
25 | openjdk7:
26 | docker:
27 | - image: jfullaondo/openjdk:7
28 | environment:
29 | MVN_EXTRA_OPTS: -Dcheckstyle.version=2.15 -Dcheckstyle.skip
30 | <<: *default_steps
31 | openjdk8:
32 | docker: &jdk8
33 | - image: cimg/openjdk:8.0
34 | <<: *default_steps
35 | openjdk11:
36 | docker:
37 | - image: cimg/openjdk:11.0
38 | <<: *default_steps
39 | openjdk13:
40 | docker:
41 | - image: cimg/openjdk:13.0
42 | <<: *default_steps
43 | openjdk17:
44 | docker:
45 | - image: cimg/openjdk:17.0
46 | <<: *default_steps
47 |
48 | ## Fails with "Source option 7 is no longer supported. Use 8 or later."
49 | # openjdk21:
50 | # docker:
51 | # - image: cimg/openjdk:21.0
52 | # <<: *default_steps
53 |
54 | windows-openjdk12:
55 | executor:
56 | # https://github.com/CircleCI-Public/windows-orb/blob/v2.4.1/src/executors/default.yml
57 | name: win/default
58 | # https://circleci.com/developer/machine/image/windows-server-2019
59 | version: 2023.04.1
60 | steps:
61 | - checkout
62 | - run: java -version
63 | - run: |
64 | choco install maven
65 | - run: |
66 | mvn clean install
67 |
68 | openjdk8-jnr-exclude:
69 | docker: *jdk8
70 | steps:
71 | - checkout
72 | - run: "mvn -Pjnr-exclude clean test"
73 | openjdk8-jnr-latest:
74 | docker: *jdk8
75 | steps:
76 | - checkout
77 | - run: "mvn clean test -DskipTests" # build main and test with default deps
78 | - run: "mvn -Pjnr-latest test" # run with modified deps
79 |
80 | workflows:
81 | version: 2
82 | agent-tests:
83 | jobs:
84 | - openjdk7
85 | - openjdk8
86 | - openjdk11
87 | - openjdk13
88 | - openjdk17
89 | # - openjdk21
90 | - windows-openjdk12
91 | - openjdk8-jnr-exclude
92 | - openjdk8-jnr-latest
93 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/about-codeowners/ for syntax
2 | # Rules are matched bottom-to-top, so one team can own subdirectories
3 | # and another team can own the rest of the directory.
4 |
5 | * @DataDog/agent-metric-pipelines
6 | *.md @DataDog/documentation
7 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "CodeQL"
3 |
4 | on:
5 | push:
6 | branches: [ master ]
7 | pull_request:
8 | # The branches below must be a subset of the branches above
9 | branches: [ master ]
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ 'java', 'python' ]
24 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
25 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
26 |
27 | steps:
28 | - name: Checkout repository
29 | uses: actions/checkout@v3
30 |
31 | # Initializes the CodeQL tools for scanning.
32 | - name: Initialize CodeQL
33 | uses: github/codeql-action/init@v2
34 | with:
35 | languages: ${{ matrix.language }}
36 | # If you wish to specify custom queries, you can do so here or in a config file.
37 | # By default, queries listed here will override any specified in a config file.
38 | # Prefix the list here with "+" to use these queries and those in the config file.
39 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
40 |
41 | - name: Autobuild
42 | uses: github/codeql-action/autobuild@v2
43 |
44 | - name: Perform CodeQL Analysis
45 | uses: github/codeql-action/analyze@v2
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | build
3 | *.iml
4 | /target/
5 | .idea
6 | .classpath
7 | .project
8 | /.settings/
9 | *.swp
10 | *.swo
11 |
12 | # jenv
13 | .java-version
14 |
15 | # rbenv for pimpmychangelog
16 | .ruby-version
17 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | stages:
2 | - deploy_to_sonatype
3 | - create_key
4 |
5 | variables:
6 | REGISTRY: registry.ddbuild.io
7 |
8 | # From the tagged repo, push the release artifact
9 | deploy_to_sonatype:
10 | stage: deploy_to_sonatype
11 |
12 | rules:
13 | # All releases are manual
14 | - when: manual
15 | allow_failure: true
16 |
17 | tags:
18 | - "runner:docker"
19 |
20 | image: maven:3.6.3-jdk-8-slim
21 |
22 | script:
23 | # Ensure we don't print commands being run to the logs during credential
24 | # operations
25 | - set +x
26 |
27 | - echo "Installing AWSCLI..."
28 | - apt update
29 | - apt install -y python3 python3-pip
30 | - python3 -m pip install awscli
31 |
32 | - echo "Fetching Sonatype user..."
33 | - export SONATYPE_USER=$(aws ssm get-parameter --region us-east-1 --name ci.java-dogstatsd-client.publishing.sonatype_username --with-decryption --query "Parameter.Value" --out text)
34 | - echo "Fetching Sonatype password..."
35 | - export SONATYPE_PASS=$(aws ssm get-parameter --region us-east-1 --name ci.java-dogstatsd-client.publishing.sonatype_password --with-decryption --query "Parameter.Value" --out text)
36 |
37 | - echo "Fetching signing key password..."
38 | - export GPG_PASSPHRASE=$(aws ssm get-parameter --region us-east-1 --name ci.java-dogstatsd-client.signing.gpg_passphrase --with-decryption --query "Parameter.Value" --out text)
39 |
40 | - echo "Fetching signing key..."
41 | - gpg_key=$(aws ssm get-parameter --region us-east-1 --name ci.java-dogstatsd-client.signing.gpg_private_key --with-decryption --query "Parameter.Value" --out text)
42 | - printf -- "$gpg_key" | gpg --import --batch
43 |
44 | - set -x
45 |
46 | - echo "Building release..."
47 | - mvn -DperformRelease=true --settings ./settings.xml clean deploy
48 |
49 | artifacts:
50 | expire_in: 6 mos
51 | paths:
52 | - ./target/*.jar
53 | - ./target/*.pom
54 | - ./target/*.asc
55 | - ./target/*.md5
56 | - ./target/*.sha1
57 | - ./target/*.sha256
58 | - ./target/*.sha512
59 |
60 | # This job creates the GPG key used to sign the releases
61 | create_key:
62 | stage: create_key
63 | when: manual
64 |
65 | tags:
66 | - "runner:docker"
67 |
68 | image: $REGISTRY/ci/agent-key-management-tools/gpg:1
69 |
70 | variables:
71 | PROJECT_NAME: "java-dogstatsd-client"
72 | EXPORT_TO_KEYSERVER: "false"
73 |
74 | script:
75 | - /create.sh
76 |
77 | artifacts:
78 | expire_in: 13 mos
79 | paths:
80 | - ./pubkeys/
81 |
--------------------------------------------------------------------------------
/DEPS.md:
--------------------------------------------------------------------------------
1 | # Overriding dependencies
2 |
3 | ## Java 8+
4 |
5 | As of version v4.1.0, this library can be used with the latest version
6 | of the `jnr-unixsocket` library.
7 |
8 | ### Maven
9 |
10 | ```xml
11 |
12 |
13 | com.datadoghq
14 | java-dogstatsd-client
15 | 4.1.0
16 |
17 |
18 | com.github.jnr
19 | jnr-unixsocket
20 | 0.38.17
21 |
22 |
23 | ```
24 |
25 | ### Gradle
26 |
27 | ```groovy
28 | dependencies {
29 | implementation('com.datadoghq:java-dogstatsd-client:4.1.0')
30 | implementation('com.github.jnr:jnr-unixsocket:0.38.17')
31 | }
32 | ```
33 |
34 | ## Without dependencies
35 |
36 | As of version v4.1.0, this library can be used without dependencies
37 | when unix sockets support is not required. Trying to instantiate a
38 | client with port set to zero to enable unix socket mode will cause a
39 | `ClassNotFound` exception.
40 |
41 | ### Maven
42 |
43 | ```xml
44 |
45 |
46 | com.datadoghq
47 | java-dogstatsd-client
48 | 4.1.0
49 |
50 |
51 | jnr-unixsocket
52 | com.github.jnr
53 |
54 |
55 |
56 |
57 | ```
58 |
59 | ### Gradle
60 |
61 | ```groovy
62 | dependencies {
63 | implementation('com.datadoghq:java-dogstatsd-client:4.1.0') {
64 | exclude group: 'com.github.jnr', module: 'jnr-unixsocket'
65 | }
66 | }
67 | ```
68 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2012 youDevise, Ltd.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Java DogStatsD Client
2 |
3 | [](https://circleci.com/gh/DataDog/java-dogstatsd-client)
4 |
5 | A DogStatsD client library implemented in Java. Allows for Java applications to easily communicate with the DataDog Agent. The library supports Java 1.7+.
6 |
7 | This version was originally forked from [java-dogstatsd-client](https://github.com/indeedeng/java-dogstatsd-client) and [java-statsd-client](https://github.com/youdevise/java-statsd-client) but it is now the canonical home for the `java-dogstatsd-client`. Collaborating with the former upstream projects we have now combined efforts to provide a single release.
8 |
9 | See [CHANGELOG.md](CHANGELOG.md) for changes.
10 |
11 | ## Installation
12 |
13 | The client jar is distributed via Maven central, and can be downloaded [from Maven](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.datadoghq%20a%3Ajava-dogstatsd-client).
14 |
15 | ```xml
16 |
17 | com.datadoghq
18 | java-dogstatsd-client
19 | 4.4.4
20 |
21 | ```
22 |
23 | ### Unix Domain Socket support
24 |
25 | As an alternative to UDP, Agent v6 can receive metrics via a UNIX Socket (on Linux only). This library supports transmission via this protocol. To use it
26 | use the `address()` method of the builder and pass the path to the socket with the `unix://` prefix:
27 |
28 | ```java
29 | StatsDClient client = new NonBlockingStatsDClientBuilder()
30 | .address("unix:///var/run/datadog/dsd.socket")
31 | .build();
32 | ```
33 |
34 | By default, all exceptions are ignored, mimicking UDP behaviour. When using Unix Sockets, transmission errors trigger exceptions you can choose to handle by passing a `StatsDClientErrorHandler`:
35 |
36 | - Connection error because of an invalid/missing socket triggers a `java.io.IOException: No such file or directory`.
37 | - If DogStatsD's reception buffer were to fill up and the non blocking client is used, the send times out after 100ms and throw either a `java.io.IOException: No buffer space available` or a `java.io.IOException: Resource temporarily unavailable`.
38 |
39 | The default UDS transport is using `SOCK_DATAGRAM` sockets. We also have experimental support for `SOCK_STREAM` sockets which can
40 | be enabled by using the `unixstream://` instead of `unix://`. This is not recommended for production use at this time.
41 |
42 | ## Configuration
43 |
44 | Once your DogStatsD client is installed, instantiate it in your code:
45 |
46 | ```java
47 | import com.timgroup.statsd.NonBlockingStatsDClientBuilder;
48 | import com.timgroup.statsd.NonBlockingStatsDClient;
49 | import com.timgroup.statsd.StatsDClient;
50 |
51 | public class DogStatsdClient {
52 |
53 | public static void main(String[] args) throws Exception {
54 |
55 | StatsDClient client = new NonBlockingStatsDClientBuilder()
56 | .prefix("statsd")
57 | .hostname("localhost")
58 | .port(8125)
59 | .build();
60 |
61 | // use your client...
62 | }
63 | }
64 | ```
65 |
66 | ### v2.x
67 |
68 | Client version `v3.x` is now preferred over the older client `v2.x` release line. We do suggest you upgrade to the newer `v3.x` at
69 | your earliest convenience.
70 |
71 | The builder pattern described above was introduced with `v2.10.0` in the `v2.x` series. Earlier releases require you use the
72 | deprecated overloaded constructors.
73 |
74 |
75 | **DEPRECATED**
76 | ```java
77 | import com.timgroup.statsd.NonBlockingStatsDClient;
78 | import com.timgroup.statsd.StatsDClient;
79 |
80 | public class DogStatsdClient {
81 |
82 | public static void main(String[] args) throws Exception {
83 |
84 | StatsDClient Statsd = new NonBlockingStatsDClient("statsd", "localhost", 8125);
85 |
86 | }
87 | }
88 | ```
89 |
90 | See the full list of available [DogStatsD Client instantiation parameters](https://docs.datadoghq.com/developers/dogstatsd/?tab=hostagent#client-instantiation-parameters).
91 |
92 | ### Migrating from 2.x to 3.x
93 |
94 | Though there are very few breaking changes in `3.x`, some code changes might be required for some user to migrate to the latest version. If you are migrating from the `v2.x` series to `v3.x` and were using the deprecated constructors, please use the following table to ease your migration to utilizing the builder pattern.
95 |
96 | | v2.x constructor parameter | v2.10.0+ builder method |
97 | |----------------------------|-------------------------|
98 | | final Callable addressLookup | NonBlockingStatsDClientBuilder addressLookup(Callable val) |
99 | | final boolean blocking | NonBlockingStatsDClientBuilder blocking(boolean val) |
100 | | final int bufferSize | NonBlockingStatsDClientBuilder socketBufferSize(int val) |
101 | | final String... constantTags | NonBlockingStatsDClientBuilder constantTags(String... val) |
102 | | final boolean enableTelemetry | NonBlockingStatsDClientBuilder enableTelemetry(boolean val) |
103 | | final String entityID | NonBlockingStatsDClientBuilder entityID(String val) |
104 | | final StatsDClientErrorHandler errorHandler | NonBlockingStatsDClientBuilder errorHandler(StatsDClientErrorHandler val) |
105 | | final String hostname | NonBlockingStatsDClientBuilder hostname(String val) |
106 | | final int maxPacketSizeBytes | NonBlockingStatsDClientBuilder maxPacketSizeBytes(String... val) |
107 | | final int processorWorkers | NonBlockingStatsDClientBuilder processorWorkers(int val) |
108 | | final int poolSize | NonBlockingStatsDClientBuilder bufferPoolSize(int val) |
109 | | final int port | NonBlockingStatsDClientBuilder port(int val) |
110 | | final String prefix | NonBlockingStatsDClientBuilder prefix(String val) |
111 | | final int queueSize | NonBlockingStatsDClientBuilder queueSize(int val) |
112 | | final int senderWorkers | NonBlockingStatsDClientBuilder senderWorkers(int val) |
113 | | final Callable telemetryAddressLookup | NonBlockingStatsDClientBuilder telemetryAddressLookup(Callable val) |
114 | | final int telemetryFlushInterval | NonBlockingStatsDClientBuilder telemetryFlushInterval(int val) |
115 | | final int timeout | NonBlockingStatsDClientBuilder timeout(int val) |
116 |
117 | ### Transport and Maximum Packet Size
118 |
119 | As mentioned above the client currently supports two forms of transport: UDP and Unix Domain Sockets (UDS).
120 |
121 | The preferred setup for local transport is UDS, while remote setups will require the use of UDP. For both setups we have tried to set convenient maximum default packet sizes that should help with performance by packing multiple statsd metrics into each network packet all while playing nicely with the respective environments. For this reason we have set the following defaults for the max packet size:
122 | - UDS: 8192 bytes - recommended default.
123 | - UDP: 1432 bytes - largest possible size given the Ethernet MTU of 1514 Bytes. This should help avoid UDP fragmentation.
124 |
125 | These are both configurable should you have other needs:
126 | ```java
127 | StatsDClient client = new NonBlockingStatsDClientBuilder()
128 | .hostname("/var/run/datadog/dsd.socket")
129 | .port(0) // Necessary for unix socket
130 | .maxPacketSizeBytes(16384) // 16kB maximum custom value
131 | .build();
132 | ```
133 |
134 | #### Origin detection over UDP and UDS
135 |
136 | Origin detection is a method to detect which pod `DogStatsD` packets are coming from in order to add the pod's tags to the tag list.
137 | The `DogStatsD` client attaches an internal tag, `entity_id`. The value of this tag is the content of the `DD_ENTITY_ID` environment variable if found, which is the pod's UID. The Datadog Agent uses this tag to add container tags to the metrics. To avoid overwriting this global tag, make sure to only `append` to the `constant_tags` list.
138 |
139 | To enable origin detection over UDP, add the following lines to your application manifest
140 | ```yaml
141 | env:
142 | - name: DD_ENTITY_ID
143 | valueFrom:
144 | fieldRef:
145 | fieldPath: metadata.uid
146 | ```
147 |
148 | ## Aggregation
149 |
150 | As of version `v2.11.0`, client-side aggregation has been introduced in the java client side for basic types (gauges, counts, sets). Aggregation remains unavailable at the
151 | time of this writing for histograms, distributions, service checks and events due to message relevance and statistical significance of these types. The feature is enabled by default as of `v3.0.0`, and remains available but disabled by default for prior versions.
152 |
153 | The goal of this feature is to reduce the number of messages submitted to the Datadog Agent. Minimizing message volume allows us to reduce load on the dogstatsd server side
154 | and mitigate packet drops. The feature has been implemented such that impact on CPU and memory should be quite minimal on the client side. Users might be concerned with
155 | what could be perceived as a loss of resolution by resorting to aggregation on the client, this should not be the case. It's worth noting the dogstatsd server implemented
156 | in the Datadog Agent already aggregates messages over a certain flush period, therefore so long as the flush interval configured on the client side is smaller
157 | than said flush interval on the server side there should no loss in resolution.
158 |
159 | ### Configuration
160 |
161 | Enabling aggregation is simple, you just need to set the appropriate options with the client builder.
162 |
163 | You can just enable aggregation by calling the `enableAggregation(bool)` method on the builder.
164 |
165 | There are two clent-side aggregation knobs available:
166 | - `aggregationShards(int)`: determines the number of shards in the aggregator, this
167 | feature is aimed at mitigating the effects of map locking in highly concurrent scenarios. Defaults to 4.
168 | - `aggregationFlushInterval(int)`: sets the period of time in milliseconds in which the
169 | aggregator will flush its metrics into the sender. Defaults to 3000 milliseconds.
170 |
171 | ```java
172 | StatsDClient client = new NonBlockingStatsDClientBuilder()
173 | .hostname("localhost")
174 | .port(8125)
175 | .enableAggregation(true)
176 | .aggregationFlushInterval(3000) // optional: in milliseconds
177 | .aggregationShards(8) // optional: defaults to 4
178 | .build();
179 | ```
180 |
181 | ## Usage
182 |
183 | In order to use DogStatsD metrics, events, and Service Checks the Agent must be [running and available](https://docs.datadoghq.com/developers/dogstatsd/).
184 |
185 | ### Metrics
186 |
187 | After the client is created, you can start sending custom metrics to Datadog. See the dedicated [Metric Submission: DogStatsD documentation](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/) to see how to submit all supported metric types to Datadog with working code examples:
188 |
189 | * [Submit a COUNT metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/#count).
190 | * [Submit a GAUGE metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/#gauge).
191 | * [Submit a HISTOGRAM metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/#histogram)
192 | * [Submit a DISTRIBUTION metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/#distribution)
193 |
194 | Some options are suppported when submitting metrics, like [applying a Sample Rate to your metrics](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/#metric-submission-options) or [tagging your metrics with your custom tags](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/#metric-tagging).
195 |
196 | ### Events
197 |
198 | After the client is created, you can start sending events to your Datadog Event Stream. See the dedicated [Event Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/events/dogstatsd/) to see how to submit an event to your Datadog Event Stream.
199 |
200 | ### Service Checks
201 |
202 | After the client is created, you can start sending Service Checks to Datadog. See the dedicated [Service Check Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/service_checks/dogstatsd_service_checks_submission/) to see how to submit a Service Check to Datadog.
203 |
--------------------------------------------------------------------------------
/THIRDPARTY.md:
--------------------------------------------------------------------------------
1 | This file lists third-party software included in
2 | 'jar-with-dependencies' binary distribution. Where multiple licenses
3 | are available, we choose the most permissive license to apply to the
4 | corresponding part of the binary distribution.
5 |
6 | For full license text and copyrights (where applicable) please refer
7 | to the [NOTICE](src/main/resources/META-INF/NOTICE) file.
8 |
9 | | Group | Artifact | Version | Developers | License | License URL |
10 | |-|-|-|-|-|-|
11 | | com.github.jnr | jffi | 1.2.23 | Wayne Meissner | The Apache Software License, Version 2.0 | http://www.apache.org/licenses/LICENSE-2.0.txt |
12 | | com.github.jnr | jnr-a64asm | 1.0.0 | ossdev | The Apache Software License, Version 2.0 | http://www.apache.org/licenses/LICENSE-2.0.txt |
13 | | com.github.jnr | jnr-constants | 0.9.17 | Wayne Meissner, Charles Oliver Nutter | The Apache Software License, Version 2.0 | http://www.apache.org/licenses/LICENSE-2.0.txt |
14 | | com.github.jnr | jnr-enxio | 0.30 | Wayne Meissner | The Apache Software License, Version 2.0 | http://www.apache.org/licenses/LICENSE-2.0.txt |
15 | | com.github.jnr | jnr-ffi | 2.1.16 | Wayne Meissner, Charles Oliver Nutter | The Apache Software License, Version 2.0 | http://www.apache.org/licenses/LICENSE-2.0.txt |
16 | | com.github.jnr | jnr-posix | 3.0.61 | Thomas E Enebo, Wayne Meissner, Charles Oliver Nutter | Eclipse Public License - v 2.0 | https://www.eclipse.org/legal/epl-2.0/ |
17 | | com.github.jnr | jnr-unixsocket | 0.36 | Wayne Meissner, Fritz Elfert | The Apache Software License, Version 2.0 | http://www.apache.org/licenses/LICENSE-2.0.txt |
18 | | com.github.jnr | jnr-x86asm | 1.0.2 | Wayne Meissner | MIT License | http://www.opensource.org/licenses/mit-license.php |
19 | | org.ow2.asm | asm | 7.1 | Eric Bruneton, Eugene Kuleshov, Remi Forax | BSD | http://asm.ow2.org/license.html |
20 | | org.ow2.asm | asm-analysis | 7.1 | Eric Bruneton, Eugene Kuleshov, Remi Forax | BSD | http://asm.ow2.org/license.html |
21 | | org.ow2.asm | asm-commons | 7.1 | Eric Bruneton, Eugene Kuleshov, Remi Forax | BSD | http://asm.ow2.org/license.html |
22 | | org.ow2.asm | asm-tree | 7.1 | Eric Bruneton, Eugene Kuleshov, Remi Forax | BSD | http://asm.ow2.org/license.html |
23 | | org.ow2.asm | asm-util | 7.1 | Eric Bruneton, Eugene Kuleshov, Remi Forax | BSD | http://asm.ow2.org/license.html |
24 |
--------------------------------------------------------------------------------
/gen_notice.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | """
4 | Generate third-party license information.
5 | - NOTICE file for the binary distribution
6 | - THIRDPARTY.md to be displayed on github
7 |
8 | When using this script to update license information, verify that the
9 | sources included (this script unpacks them into target/dependency)
10 | matches the license.
11 | """
12 |
13 | import functools
14 | import os, os.path, re
15 | import xml.etree.ElementTree as ElementTree
16 | import urllib.request
17 | from collections import namedtuple
18 |
19 | Dep = namedtuple('Dep', ['groupId', 'artifactId', 'version'])
20 | def list_dependencies():
21 | pattern = re.compile(".* (?P[^:]*):(?P[^:]*):jar:(?P[^:]*):compile")
22 | with os.popen("mvn dependency:list") as l:
23 | for line in l:
24 | m = pattern.match(line)
25 | if not m:
26 | continue
27 | yield Dep(**{k: m[k] for k in ['groupId', 'artifactId', 'version']})
28 |
29 | def unpack_dependencies():
30 | if not os.path.isdir("target/dependency"):
31 | os.system("mvn dependency:copy-dependencies -Dmdep.copyPom -Dmdep.useRepositoryLayout")
32 | os.system("mvn dependency:unpack-dependencies -DincludeScope=compile -Dmdep.useRepositoryLayout")
33 | os.system("mvn dependency:unpack-dependencies -Dclassifier=sources -DincludeScope=compile -Dmdep.useRepositoryLayout")
34 |
35 | def dep_path(dep):
36 | return os.path.join(
37 | 'target', 'dependency',
38 | *dep.groupId.split('.'),
39 | dep.artifactId,
40 | dep.version)
41 |
42 | def scan_unpacked(dep, ignored={'module-info.class'}):
43 | classdirs = set()
44 | prefix = dep_path(dep)
45 | for dirpath, _, filenames in os.walk(prefix):
46 | for f in filenames:
47 | if f.endswith('.class') and f not in ignored:
48 | if dirpath.removeprefix(prefix).removeprefix('/') == '':
49 | raise ValueError(dirpath, prefix)
50 | classdirs.add(dirpath.removeprefix(prefix).removeprefix('/'))
51 |
52 | return unify_paths(classdirs)
53 |
54 | def unify_paths(paths):
55 | res = set()
56 | for p in sorted(paths):
57 | if not any(p.startswith(r) for r in res):
58 | res.add(p)
59 | return res
60 |
61 | Pom = namedtuple('Pom', ['authors', 'license'])
62 | PomLicense = namedtuple('PomLicense', ['name', 'url'])
63 | def read_pom(dep, ns={'pom': 'http://maven.apache.org/POM/4.0.0'}):
64 | path = os.path.join(dep_path(dep), f"{dep.artifactId}-{dep.version}.pom")
65 | pom = ElementTree.parse(path)
66 | auth = pom.findall('.//pom:developers/pom:developer/pom:name', ns)
67 | auth = ', '.join(a.text for a in auth)
68 | lic = pom.find('.//pom:licenses/pom:license', ns)
69 | return Pom(auth, PomLicense(
70 | lic.findtext('./pom:name', None, ns),
71 | lic.findtext('./pom:url', None, ns),
72 | ))
73 |
74 | LICENSES = {
75 | # Links to the project front-page.
76 | 'org.ow2.asm': 'https://raw.githubusercontent.com/llbit/ow2-asm/master/LICENSE.txt',
77 | # These two link to generic license text without copyrights.
78 | 'com.github.jnr/jnr-posix': 'https://raw.githubusercontent.com/jnr/jnr-posix/jnr-posix-{version}/LICENSE.txt',
79 | 'com.github.jnr/jnr-x86asm': 'https://raw.githubusercontent.com/jnr/jnr-x86asm/{version}/LICENSE',
80 | }
81 |
82 | @functools.cache
83 | def fetch(url):
84 | print(f'... fetching {url}')
85 | with urllib.request.urlopen(url) as f:
86 | return clean(f.read().decode('ascii'))
87 |
88 | def clean(s, remove=re.compile('[^\n\t !-~]', re.A)):
89 | return remove.sub('', s)
90 |
91 | def write_notice(notice, dep, classdirs, license_text):
92 | """
93 | NOTICE file to be included with the binary distribution and
94 | fullfill third-party license requirements.
95 | """
96 | header = f'{dep.groupId} {dep.artifactId} {dep.version}'
97 | print(header, file=notice)
98 | print('-' * len(header), file=notice)
99 |
100 | repo_path = '/'.join([*dep.groupId.split('.'), dep.artifactId, dep.version])
101 | print(f'Sources are available at https://repo.maven.apache.org/maven2/{repo_path}/\n', file=notice)
102 |
103 | print('Files in the following directories:', ' '.join(sorted(classdirs)), file=notice)
104 | print('are distributed under the terms of the following license:\n', file=notice)
105 |
106 | print(license_text, file=notice)
107 | print('\n', file=notice)
108 |
109 | LISTING_HEADER = """\
110 | This file lists third-party software included in
111 | 'jar-with-dependencies' binary distribution. Where multiple licenses
112 | are available, we choose the most permissive license to apply to the
113 | corresponding part of the binary distribution.
114 |
115 | For full license text and copyrights (where applicable) please refer
116 | to the [NOTICE](src/main/resources/META-INF/NOTICE) file.
117 |
118 | | Group | Artifact | Version | Developers | License | License URL |
119 | |-|-|-|-|-|-|
120 | """
121 |
122 | def write_listing(listing, dep, pom):
123 | """
124 | THIRDPARTY.md provides an overview of third-party licenses that
125 | will apply to the binary distribution.
126 | """
127 | print(f'| {dep.groupId} | {dep.artifactId} | {dep.version} ', end='', file=listing)
128 | print(f'| {pom.authors} ', end='', file=listing)
129 | print(f'| {pom.license.name} | {pom.license.url} ', end='', file=listing)
130 | print(f'|', file=listing)
131 |
132 | def gen_all(notice, listing):
133 | deps = list_dependencies()
134 | unpack_dependencies()
135 |
136 | print(LISTING_HEADER, end='', file=listing)
137 |
138 | for dep in sorted(deps):
139 | classdirs = scan_unpacked(dep)
140 | pom = read_pom(dep)
141 |
142 | license_url = LICENSES.get(f'{dep.groupId}/{dep.artifactId}') or LICENSES.get(dep.groupId)
143 | if license_url:
144 | license_url = license_url.format(**dep._asdict())
145 | else:
146 | license_url = pom.license.url
147 | if not license_url:
148 | print(f'License is missing for {dep}')
149 | break
150 |
151 | license_text = fetch(license_url)
152 |
153 | write_notice(notice, dep, classdirs, license_text)
154 | write_listing(listing, dep, pom)
155 |
156 | if __name__ == '__main__':
157 | with open('src/main/resources/META-INF/NOTICE', 'w') as notice:
158 | with open('THIRDPARTY.md', 'w') as listing:
159 | gen_all(notice, listing)
160 |
--------------------------------------------------------------------------------
/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | nexus
8 | ${env.SONATYPE_USER}
9 | ${env.SONATYPE_PASS}
10 |
11 |
12 | github
13 | datadog
14 | ${env.GITHUB_TOKEN}
15 |
16 |
17 | gpg.passphrase
18 | ${env.GPG_PASSPHRASE}
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/AlphaNumericMessage.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | public abstract class AlphaNumericMessage extends Message {
4 |
5 | protected final String value;
6 |
7 | protected AlphaNumericMessage(Message.Type type, String value) {
8 | super(type);
9 | this.value = value;
10 | }
11 |
12 | protected AlphaNumericMessage(String aspect, Message.Type type, String value, String[] tags) {
13 | super(aspect, type, tags);
14 | this.value = value;
15 | }
16 |
17 | /**
18 | * Aggregate message.
19 | *
20 | * @param message
21 | * Message to aggregate.
22 | */
23 | @Override
24 | public void aggregate(Message message) { }
25 |
26 | /**
27 | * Get underlying message value.
28 | *
29 | * @return returns the value for the Message
30 | */
31 | public String getValue() {
32 | return this.value;
33 | }
34 |
35 | @Override
36 | public int hashCode() {
37 | return super.hashCode() * HASH_MULTIPLIER + this.value.hashCode();
38 | }
39 |
40 | @Override
41 | public boolean equals(Object object) {
42 | boolean equal = super.equals(object);
43 | if (!equal) {
44 | return false;
45 | }
46 |
47 | if (object instanceof AlphaNumericMessage ) {
48 | AlphaNumericMessage msg = (AlphaNumericMessage)object;
49 | return this.value.equals(msg.getValue());
50 | }
51 |
52 | return false;
53 | }
54 |
55 | @Override
56 | public int compareTo(Message message) {
57 | int comparison = super.compareTo(message);
58 | if (comparison == 0 && message instanceof AlphaNumericMessage) {
59 | return value.compareTo(((AlphaNumericMessage) message).getValue());
60 | }
61 | return comparison;
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/BufferPool.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | import java.util.concurrent.ArrayBlockingQueue;
6 | import java.util.concurrent.BlockingQueue;
7 |
8 | public class BufferPool {
9 | private final BlockingQueue pool;
10 | private final int size;
11 | private final int bufferSize;
12 | private final boolean direct;
13 |
14 |
15 | BufferPool(final int poolSize, int bufferSize, final boolean direct) throws InterruptedException {
16 |
17 | size = poolSize;
18 | this.bufferSize = bufferSize;
19 | this.direct = direct;
20 |
21 | pool = new ArrayBlockingQueue(poolSize);
22 | for (int i = 0; i < size ; i++) {
23 | if (direct) {
24 | pool.put(ByteBuffer.allocateDirect(bufferSize));
25 | } else {
26 | pool.put(ByteBuffer.allocate(bufferSize));
27 | }
28 | }
29 | }
30 |
31 | BufferPool(final BufferPool pool) throws InterruptedException {
32 | this.size = pool.size;
33 | this.bufferSize = pool.bufferSize;
34 | this.direct = pool.direct;
35 | this.pool = new ArrayBlockingQueue(pool.size);
36 | for (int i = 0; i < size ; i++) {
37 | if (direct) {
38 | this.pool.put(ByteBuffer.allocateDirect(bufferSize));
39 | } else {
40 | this.pool.put(ByteBuffer.allocate(bufferSize));
41 | }
42 | }
43 | }
44 |
45 | ByteBuffer borrow() throws InterruptedException {
46 | return pool.take();
47 | }
48 |
49 | void put(ByteBuffer buffer) throws InterruptedException {
50 | pool.put(buffer);
51 | }
52 |
53 | int getSize() {
54 | return size;
55 | }
56 |
57 | int getBufferSize() {
58 | return bufferSize;
59 | }
60 |
61 | int available() {
62 | return pool.size();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/CgroupReader.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.StringReader;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.Paths;
9 | import java.util.Arrays;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 |
16 | /**
17 | * A reader class that retrieves the current container ID or the cgroup controller
18 | * inode parsed from the cgroup file.
19 | *
20 | */
21 | class CgroupReader {
22 | private static final Path CGROUP_PATH = Paths.get("/proc/self/cgroup");
23 | /**
24 | * DEFAULT_CGROUP_MOUNT_PATH is the default cgroup mount path.
25 | **/
26 | private static final Path DEFAULT_CGROUP_MOUNT_PATH = Paths.get("/sys/fs/cgroup");
27 | /**
28 | * CGROUP_NS_PATH is the path to the cgroup namespace file.
29 | **/
30 | private static final Path CGROUP_NS_PATH = Paths.get("/proc/self/ns/cgroup");
31 | private static final String CONTAINER_SOURCE = "[0-9a-f]{64}";
32 | private static final String TASK_SOURCE = "[0-9a-f]{32}-\\d+";
33 | private static final Pattern LINE_RE = Pattern.compile("^\\d+:[^:]*:(.+)$", Pattern.MULTILINE | Pattern.UNIX_LINES);
34 | private static final Pattern CONTAINER_RE = Pattern.compile(
35 | "(" + CONTAINER_SOURCE + "|" + TASK_SOURCE + ")(?:.scope)?$");
36 |
37 | /**
38 | * CGROUPV1_BASE_CONTROLLER is the controller used to identify the container-id
39 | * in cgroup v1 (memory).
40 | **/
41 | private static final String CGROUPV1_BASE_CONTROLLER = "memory";
42 | /**
43 | * CGROUPV2_BASE_CONTROLLER is the controller used to identify the container-id
44 | * in cgroup v2.
45 | **/
46 | private static final String CGROUPV2_BASE_CONTROLLER = "";
47 | /**
48 | * HOST_CGROUP_NAMESPACE_INODE is the inode of the host cgroup namespace.
49 | **/
50 | private static final long HOST_CGROUP_NAMESPACE_INODE = 0xEFFFFFFBL;
51 |
52 | private boolean readOnce = false;
53 | /**
54 | * containerID holds either the container ID or the cgroup controller inode.
55 | **/
56 | public String containerID;
57 |
58 | /**
59 | * Returns the container ID if available or the cgroup controller inode.
60 | *
61 | * @throws IOException if /proc/self/cgroup is readable and still an I/O error
62 | * occurs reading from the stream.
63 | */
64 | public String getContainerID() throws IOException {
65 | if (readOnce) {
66 | return containerID;
67 | }
68 |
69 | final String cgroupContent = read(CGROUP_PATH);
70 | if (cgroupContent == null || cgroupContent.isEmpty()) {
71 | return null;
72 | }
73 | containerID = parse(cgroupContent);
74 | /*
75 | * If the container ID is not available it means that the application is either
76 | * not running in a container or running is private cgroup namespace, we
77 | * fallback to the cgroup controller inode. The agent (7.51+) will use it to get
78 | * the container ID.
79 | * In Host cgroup namespace, the container ID should be found. If it is not
80 | * found, it means that the application is running on a host/vm.
81 | *
82 | */
83 | if ((containerID == null || containerID.equals("")) && !isHostCgroupNamespace(CGROUP_NS_PATH)) {
84 | containerID = getCgroupInode(DEFAULT_CGROUP_MOUNT_PATH, cgroupContent);
85 | }
86 | return containerID;
87 | }
88 |
89 | /**
90 | * Returns the content of `path` (=/proc/self/cgroup).
91 | *
92 | * @throws IOException if /proc/self/cgroup is readable and still an I/O error
93 | * occurs reading from the stream.
94 | */
95 | private String read(Path path) throws IOException {
96 | readOnce = true;
97 | if (!Files.isReadable(path)) {
98 | return null;
99 | }
100 |
101 | return new String(Files.readAllBytes(path));
102 | }
103 |
104 | /**
105 | * Parses a Cgroup file (=/proc/self/cgroup) content and returns the
106 | * corresponding container ID. It can be found only if the container
107 | * is running in host cgroup namespace.
108 | *
109 | * @param cgroupsContent Cgroup file content
110 | */
111 | public static String parse(final String cgroupsContent) {
112 | final Matcher lines = LINE_RE.matcher(cgroupsContent);
113 | while (lines.find()) {
114 | final String path = lines.group(1);
115 | final Matcher matcher = CONTAINER_RE.matcher(path);
116 | if (matcher.find()) {
117 | return matcher.group(1);
118 | }
119 | }
120 |
121 | return null;
122 | }
123 |
124 | /**
125 | * Returns true if the host cgroup namespace is used.
126 | * It looks at the inode of `/proc/self/ns/cgroup` and compares it to
127 | * HOST_CGROUP_NAMESPACE_INODE.
128 | *
129 | * @param path Path to the cgroup namespace file.
130 | */
131 | private static boolean isHostCgroupNamespace(final Path path) {
132 | long hostCgroupInode = inodeForPath(path);
133 | return hostCgroupInode == HOST_CGROUP_NAMESPACE_INODE;
134 | }
135 |
136 | /**
137 | * Returns the inode for the given path.
138 | *
139 | * @param path Path to the cgroup namespace file.
140 | */
141 | private static long inodeForPath(final Path path) {
142 | try {
143 | long inode = (long) Files.getAttribute(path, "unix:ino");
144 | return inode;
145 | } catch (Exception e) {
146 | return 0;
147 | }
148 | }
149 |
150 | /**
151 | * Returns the cgroup controller inode for the given cgroup mount path and
152 | * procSelfCgroupPath.
153 | *
154 | * @param cgroupMountPath Path to the cgroup mount point.
155 | * @param cgroupContent String content of the cgroup file.
156 | */
157 | public static String getCgroupInode(final Path cgroupMountPath, final String cgroupContent) throws IOException {
158 | Map cgroupControllersPaths = parseCgroupNodePath(cgroupContent);
159 | if (cgroupControllersPaths == null) {
160 | return null;
161 | }
162 |
163 | // Retrieve the cgroup inode from /sys/fs/cgroup+controller+cgroupNodePath
164 | List controllers = Arrays.asList(CGROUPV1_BASE_CONTROLLER, CGROUPV2_BASE_CONTROLLER);
165 | for (String controller : controllers) {
166 | String cgroupNodePath = cgroupControllersPaths.get(controller);
167 | if (cgroupNodePath == null) {
168 | continue;
169 | }
170 | Path path = Paths.get(cgroupMountPath.toString(), controller, cgroupNodePath);
171 | long inode = inodeForPath(path);
172 | /*
173 | * Inode 0 is not a valid inode. Inode 1 is a bad block inode and inode 2 is the
174 | * root of a filesystem. We can safely ignore them.
175 | */
176 | if (inode > 2) {
177 | return "in-" + inode;
178 | }
179 | }
180 |
181 | return null;
182 | }
183 |
184 | /**
185 | * Returns a map of cgroup controllers and their corresponding cgroup path.
186 | *
187 | * @param cgroupContent Cgroup file content.
188 | */
189 | public static Map parseCgroupNodePath(final String cgroupContent) throws IOException {
190 | Map res = new HashMap<>();
191 | BufferedReader br = new BufferedReader(new StringReader(cgroupContent));
192 |
193 | String line;
194 | while ((line = br.readLine()) != null) {
195 | String[] tokens = line.split(":");
196 | if (tokens.length != 3) {
197 | continue;
198 | }
199 | if (CGROUPV1_BASE_CONTROLLER.equals(tokens[1]) || CGROUPV2_BASE_CONTROLLER.equals(tokens[1])) {
200 | res.put(tokens[1], tokens[2]);
201 | }
202 | }
203 |
204 | br.close();
205 | return res;
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/ClientChannel.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.nio.channels.WritableByteChannel;
4 |
5 | interface ClientChannel extends WritableByteChannel {
6 | String getTransportType();
7 |
8 | int getMaxPacketSizeBytes();
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/DatagramClientChannel.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.io.IOException;
4 | import java.net.SocketAddress;
5 | import java.nio.ByteBuffer;
6 | import java.nio.channels.DatagramChannel;
7 |
8 | class DatagramClientChannel implements ClientChannel {
9 | protected final DatagramChannel delegate;
10 | private final SocketAddress address;
11 |
12 | /**
13 | * Creates a new DatagramClientChannel using the default DatagramChannel.
14 | * @param address Address to connect the channel to
15 | * @throws IOException if an I/O error occurs
16 | */
17 | DatagramClientChannel(SocketAddress address) throws IOException {
18 | this(DatagramChannel.open(), address);
19 | }
20 |
21 | /**
22 | * Creates a new DatagramClientChannel that wraps the delegate.
23 | * @param delegate Implementation this instance wraps
24 | * @param address Address to connect the channel to
25 | */
26 | DatagramClientChannel(DatagramChannel delegate, SocketAddress address) {
27 | this.delegate = delegate;
28 | this.address = address;
29 | }
30 |
31 | @Override
32 | public boolean isOpen() {
33 | return delegate.isOpen();
34 | }
35 |
36 | @Override
37 | public int write(ByteBuffer src) throws IOException {
38 | return delegate.send(src, address);
39 | }
40 |
41 | @Override
42 | public void close() throws IOException {
43 | delegate.close();
44 | }
45 |
46 | @Override
47 | public String getTransportType() {
48 | return "udp";
49 | }
50 |
51 | @Override
52 | public String toString() {
53 | return "[" + getTransportType() + "] " + address;
54 | }
55 |
56 | @Override
57 | public int getMaxPacketSizeBytes() {
58 | return NonBlockingStatsDClient.DEFAULT_UDP_MAX_PACKET_SIZE_BYTES;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/DirectStatsDClient.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | /**
4 | * DirectStatsDClient is an experimental extension of {@link StatsDClient} that allows for direct access to some
5 | * dogstatsd features.
6 | *
7 | *
It is not recommended to use this client in production. This client might allow you to take advantage of
8 | * new features in the agent before they are released, but it might also break your application.
9 | */
10 | public interface DirectStatsDClient extends StatsDClient {
11 |
12 | /**
13 | * Records values for the specified named distribution.
14 | *
15 | *
The method doesn't take care of breaking down the values array if it is too large. It's up to the caller to
16 | * make sure the size is kept reasonable.
17 | *
18 | *
This method is a DataDog extension, and may not work with other servers.
19 | *
20 | *
This method is non-blocking and is guaranteed not to throw an exception.
21 | *
22 | * @param aspect the name of the distribution
23 | * @param values the values to be incorporated in the distribution
24 | * @param sampleRate percentage of time metric to be sent
25 | * @param tags array of tags to be added to the data
26 | */
27 | void recordDistributionValues(String aspect, double[] values, double sampleRate, String... tags);
28 |
29 |
30 | /**
31 | * Records values for the specified named distribution.
32 | *
33 | *
The method doesn't take care of breaking down the values array if it is too large. It's up to the caller to
34 | * make sure the size is kept reasonable.
35 | *
36 | *
This method is a DataDog extension, and may not work with other servers.
37 | *
38 | *
This method is non-blocking and is guaranteed not to throw an exception.
39 | *
40 | * @param aspect the name of the distribution
41 | * @param values the values to be incorporated in the distribution
42 | * @param sampleRate percentage of time metric to be sent
43 | * @param tags array of tags to be added to the data
44 | */
45 | void recordDistributionValues(String aspect, long[] values, double sampleRate, String... tags);
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/Event.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.util.Date;
4 |
5 | /**
6 | * An event to send.
7 | * @see http://docs.datadoghq.com/guides/dogstatsd/#events
8 | */
9 | public class Event {
10 | private String title;
11 | private String text;
12 | private long millisSinceEpoch = -1;
13 | private String hostname;
14 | private String aggregationKey;
15 | private String priority;
16 | private String sourceTypeName;
17 | private String alertType;
18 |
19 | public String getTitle() {
20 | return title;
21 | }
22 |
23 | public String getText() {
24 | return text;
25 | }
26 |
27 | /**
28 | * Get number of milliseconds since epoch started.
29 | * @return -1 if not set
30 | */
31 | public long getMillisSinceEpoch() {
32 | return millisSinceEpoch;
33 | }
34 |
35 | public String getHostname() {
36 | return hostname;
37 | }
38 |
39 | public String getAggregationKey() {
40 | return aggregationKey;
41 | }
42 |
43 | public String getPriority() {
44 | return priority;
45 | }
46 |
47 | public String getSourceTypeName() {
48 | return sourceTypeName;
49 | }
50 |
51 | public String getAlertType() {
52 | return alertType;
53 | }
54 |
55 | public static Builder builder() {
56 | return new Builder();
57 | }
58 |
59 | private Event(){}
60 |
61 | public enum Priority {
62 | LOW, NORMAL
63 | }
64 |
65 | public enum AlertType {
66 | ERROR, WARNING, INFO, SUCCESS
67 | }
68 |
69 | @SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject",
70 | "PrivateMemberAccessBetweenOuterAndInnerClass", "ParameterHidesMemberVariable"})
71 | public static class Builder {
72 | private final Event event = new Event();
73 |
74 | private Builder() {}
75 |
76 | /**
77 | * Build factory method for the event.
78 | * @return Event built following specified options.
79 | */
80 | public Event build() {
81 | if ((event.title == null) || event.title.isEmpty()) {
82 | throw new IllegalStateException("event title must be set");
83 | }
84 | return event;
85 | }
86 |
87 | /**
88 | * Title for the event.
89 | * @param title
90 | * Event title ; mandatory
91 | * @return Builder object being used.
92 | */
93 | public Builder withTitle(final String title) {
94 | event.title = title;
95 | return this;
96 | }
97 |
98 | /**
99 | * Text for the event.
100 | * @param text
101 | * Event text ; supports line breaks ; mandatory
102 | * @return Builder object being used.
103 | */
104 | public Builder withText(final String text) {
105 | event.text = text;
106 | return this;
107 | }
108 |
109 | /**
110 | * Date for the event.
111 | * @param date
112 | * Assign a timestamp to the event ; Default: none (Default is the current Unix epoch timestamp when not sent)
113 | * @return Builder object being used.
114 | */
115 | public Builder withDate(final Date date) {
116 | event.millisSinceEpoch = date.getTime();
117 | return this;
118 | }
119 |
120 | /**
121 | * Date for the event.
122 | * @param millisSinceEpoch
123 | * Assign a timestamp to the event ; Default: none (Default is the current Unix epoch timestamp when not sent)
124 | * @return Builder object being used.
125 | */
126 | public Builder withDate(final long millisSinceEpoch) {
127 | event.millisSinceEpoch = millisSinceEpoch;
128 | return this;
129 | }
130 |
131 | /**
132 | * Source hostname for the event.
133 | * @param hostname
134 | * Assign a hostname to the event ; Default: none
135 | * @return Builder object being used.
136 | */
137 | public Builder withHostname(final String hostname) {
138 | event.hostname = hostname;
139 | return this;
140 | }
141 |
142 | /**
143 | * Aggregation key for the event.
144 | * @param aggregationKey
145 | * Assign an aggregation key to the event, to group it with some others ; Default: none
146 | * @return Builder object being used.
147 | */
148 | public Builder withAggregationKey(final String aggregationKey) {
149 | event.aggregationKey = aggregationKey;
150 | return this;
151 | }
152 |
153 | /**
154 | * Priority for the event.
155 | * @param priority
156 | * Can be "normal" or "low" ; Default: "normal"
157 | * @return Builder object being used.
158 | */
159 | public Builder withPriority(final Priority priority) {
160 | //noinspection StringToUpperCaseOrToLowerCaseWithoutLocale
161 | event.priority = priority.name().toLowerCase();
162 | return this;
163 | }
164 |
165 | /**
166 | * Source Type name for the event.
167 | * @param sourceTypeName
168 | * Assign a source type to the event ; Default: none
169 | * @return Builder object being used.
170 | */
171 | public Builder withSourceTypeName(final String sourceTypeName) {
172 | event.sourceTypeName = sourceTypeName;
173 | return this;
174 | }
175 |
176 | /**
177 | * Alert type for the event.
178 | * @param alertType
179 | * Can be "error", "warning", "info" or "success" ; Default: "info"
180 | * @return Builder object being used.
181 | */
182 | public Builder withAlertType(final AlertType alertType) {
183 | //noinspection StringToUpperCaseOrToLowerCaseWithoutLocale
184 | event.alertType = alertType.name().toLowerCase();
185 | return this;
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/InvalidMessageException.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | /**
4 | * Signals that we've been passed a message that's invalid and won't be sent.
5 | *
6 | * @author Taylor Schilling
7 | */
8 |
9 | public class InvalidMessageException extends RuntimeException {
10 |
11 | private final String invalidMessage;
12 |
13 | /**
14 | * Creates an InvalidMessageException with a specified detail message and the invalid message itself.
15 | *
16 | * @param detailMessage a message that details why the invalid message is considered so
17 | * @param invalidMessage the message deemed invalid
18 | */
19 | public InvalidMessageException(final String detailMessage, final String invalidMessage) {
20 | super(detailMessage);
21 | this.invalidMessage = invalidMessage;
22 | }
23 |
24 | public String getInvalidMessage() {
25 | return invalidMessage;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/MapUtils.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.invoke.MethodHandles;
5 | import java.lang.invoke.MethodType;
6 | import java.util.Map;
7 |
8 | /**
9 | * MethodHandle based bridge for using JDK8+ functionality at JDK7 language level.
10 | * Can be removed when support for JDK7 is dropped.
11 | */
12 | public class MapUtils {
13 |
14 | private static final MethodHandle MAP_PUT_IF_ABSENT = buildMapPutIfAbsent();
15 |
16 | /**
17 | * Emulates {@code Map.putIfAbsent} semantics. Replace when baselining at JDK8+.
18 | * @return the previous value associated with the message, or null if the value was not seen before
19 | */
20 | static Message putIfAbsent(Map map, Message message) {
21 | if (MAP_PUT_IF_ABSENT != null) {
22 | try {
23 | return (Message) (Object) MAP_PUT_IF_ABSENT.invokeExact(map, (Object) message, (Object) message);
24 | } catch (Throwable ignore) {
25 | return putIfAbsentFallback(map, message);
26 | }
27 | }
28 | return putIfAbsentFallback(map, message);
29 | }
30 |
31 | /**
32 | * Emulates {@code Map.putIfAbsent} semantics. Replace when baselining at JDK8+.
33 | * @return the previous value associated with the message, or null if the value was not seen before
34 | */
35 | private static Message putIfAbsentFallback(Map map, Message message) {
36 | if (map.containsKey(message)) {
37 | return map.get(message);
38 | }
39 | map.put(message, message);
40 | return null;
41 | }
42 |
43 | private static MethodHandle buildMapPutIfAbsent() {
44 | try {
45 | return MethodHandles.publicLookup()
46 | .findVirtual(Map.class, "putIfAbsent",
47 | MethodType.methodType(Object.class, Object.class, Object.class));
48 | } catch (Throwable ignore) {
49 | return null;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/Message.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.util.Arrays;
4 | import java.util.EnumSet;
5 | import java.util.Objects;
6 | import java.util.Set;
7 |
8 | public abstract class Message implements Comparable {
9 |
10 | final String aspect;
11 | final Message.Type type;
12 | final String[] tags;
13 | protected boolean done;
14 |
15 | // borrowed from Array.hashCode implementation:
16 | // https://github.com/openjdk/jdk11/blob/master/src/java.base/share/classes/java/util/Arrays.java#L4454-L4465
17 | protected static final int HASH_MULTIPLIER = 31;
18 |
19 | public enum Type {
20 | GAUGE("g"),
21 | COUNT("c"),
22 | TIME("ms"),
23 | SET("s"),
24 | HISTOGRAM("h"),
25 | DISTRIBUTION("d"),
26 | EVENT("_e"),
27 | SERVICE_CHECK("_sc");
28 |
29 | private final String type;
30 |
31 | Type(String type) {
32 | this.type = type;
33 | }
34 |
35 | public String toString() {
36 | return this.type;
37 | }
38 | }
39 |
40 | protected static final Set AGGREGATE_SET = EnumSet.of(Type.COUNT, Type.GAUGE, Type.SET);
41 |
42 | protected Message(Message.Type type) {
43 | this("", type, null);
44 | }
45 |
46 | protected Message(String aspect, Message.Type type, String[] tags) {
47 | this.aspect = aspect == null ? "" : aspect;
48 | this.type = type;
49 | this.done = false;
50 | this.tags = tags;
51 | }
52 |
53 | /**
54 | * Write this message to the provided {@link StringBuilder}. Will
55 | * be called from the sender threads.
56 | *
57 | * @param builder StringBuilder the text representation will be written to.
58 | * @param capacity The capacity of the send buffer.
59 | * @param containerID The container ID to be appended to the message.
60 | * @return boolean indicating whether the message was partially written to the builder.
61 | * If true, the method will be called again with the same arguments to continue writing.
62 | */
63 | abstract boolean writeTo(StringBuilder builder, int capacity, String containerID);
64 |
65 | /**
66 | * Aggregate message.
67 | *
68 | * @param message
69 | * Message to aggregate.
70 | */
71 | public abstract void aggregate(Message message);
72 |
73 |
74 | /**
75 | * Return the message aspect.
76 | *
77 | * @return returns the string representing the Message aspect
78 | */
79 | public final String getAspect() {
80 | return this.aspect;
81 | }
82 |
83 | /**
84 | * Return the message type.
85 | *
86 | * @return returns the dogstatsd type for the Message
87 | */
88 | public final Type getType() {
89 | return this.type;
90 | }
91 |
92 | /**
93 | * Return the array of tags for the message.
94 | *
95 | * @return returns the string array of tags for the Message
96 | */
97 | public String[] getTags() {
98 | return this.tags;
99 | }
100 |
101 | /**
102 | * Return whether a message can be aggregated.
103 | *
104 | * @return boolean on whether or not this message type may be aggregated.
105 | */
106 | public boolean canAggregate() {
107 | return AGGREGATE_SET.contains(type);
108 | }
109 |
110 | public void setDone(boolean done) {
111 | this.done = done;
112 | }
113 |
114 | public boolean getDone() {
115 | return this.done;
116 | }
117 |
118 | /**
119 | * Messages must implement hashCode.
120 | */
121 | @Override
122 | public int hashCode() {
123 | return type.hashCode() * HASH_MULTIPLIER * HASH_MULTIPLIER
124 | + aspect.hashCode() * HASH_MULTIPLIER
125 | + Arrays.hashCode(this.tags);
126 | }
127 |
128 | /**
129 | * Messages must implement hashCode.
130 | */
131 | @Override
132 | public boolean equals(Object object) {
133 | if (object == this) {
134 | return true;
135 | }
136 | if (object instanceof Message) {
137 | final Message msg = (Message)object;
138 |
139 | return (Objects.equals(this.getAspect(), msg.getAspect()))
140 | && (this.getType() == msg.getType())
141 | && Arrays.equals(this.tags, msg.getTags());
142 | }
143 |
144 | return false;
145 | }
146 |
147 | @Override
148 | public int compareTo(Message message) {
149 | int typeComparison = getType().compareTo(message.getType());
150 | if (typeComparison == 0) {
151 | int aspectComparison = getAspect().compareTo(message.getAspect());
152 | if (aspectComparison == 0) {
153 | if (tags == null && message.tags == null) {
154 | return 0;
155 | } else if (tags == null) {
156 | return 1;
157 | } else if (message.tags == null) {
158 | return -1;
159 | }
160 | if (tags.length == message.tags.length) {
161 | int comparison = 0;
162 | for (int i = 0; i < tags.length && comparison == 0; i++) {
163 | comparison = tags[i].compareTo(message.tags[i]);
164 | }
165 | return comparison;
166 | }
167 | return tags.length < message.tags.length ? 1 : -1;
168 | }
169 | return aspectComparison;
170 | }
171 | return typeComparison;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/NamedPipeClientChannel.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.io.FileNotFoundException;
4 | import java.io.IOException;
5 | import java.io.RandomAccessFile;
6 | import java.nio.ByteBuffer;
7 | import java.nio.channels.FileChannel;
8 |
9 | class NamedPipeClientChannel implements ClientChannel {
10 | private final RandomAccessFile randomAccessFile;
11 | private final FileChannel fileChannel;
12 | private final String pipe;
13 |
14 | /**
15 | * Creates a new NamedPipeClientChannel with the given address.
16 | *
17 | * @param address Location of named pipe
18 | * @throws FileNotFoundException if pipe does not exist
19 | */
20 | NamedPipeClientChannel(NamedPipeSocketAddress address) throws FileNotFoundException {
21 | pipe = address.getPipe();
22 | randomAccessFile = new RandomAccessFile(pipe, "rw");
23 | fileChannel = randomAccessFile.getChannel();
24 | }
25 |
26 | @Override
27 | public boolean isOpen() {
28 | return fileChannel.isOpen();
29 | }
30 |
31 | @Override
32 | public int write(ByteBuffer src) throws IOException {
33 | return fileChannel.write(src);
34 | }
35 |
36 | @Override
37 | public void close() throws IOException {
38 | // closing the file also closes the channel
39 | randomAccessFile.close();
40 | }
41 |
42 | @Override
43 | public String getTransportType() {
44 | return "namedpipe";
45 | }
46 |
47 | @Override
48 | public String toString() {
49 | return pipe;
50 | }
51 |
52 | @Override
53 | public int getMaxPacketSizeBytes() {
54 | return NonBlockingStatsDClient.DEFAULT_UDS_MAX_PACKET_SIZE_BYTES;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/NamedPipeSocketAddress.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.net.SocketAddress;
4 |
5 | public class NamedPipeSocketAddress extends SocketAddress {
6 | private static final String NAMED_PIPE_PREFIX = "\\\\.\\pipe\\";
7 | private final String pipe;
8 |
9 | public NamedPipeSocketAddress(String pipeName) {
10 | this.pipe = normalizePipeName(pipeName);
11 | }
12 |
13 | public String getPipe() {
14 | return pipe;
15 | }
16 |
17 | /**
18 | * Return true if object is a NamedPipeSocketAddress referring to the same path.
19 | */
20 | public boolean equals(Object object) {
21 | if (object instanceof NamedPipeSocketAddress) {
22 | return pipe.equals(((NamedPipeSocketAddress)object).pipe);
23 | }
24 | return false;
25 | }
26 |
27 | /**
28 | * A normalized version of the pipe name that includes the `\\.\pipe\` prefix
29 | */
30 | static String normalizePipeName(String pipeName) {
31 | if (pipeName.startsWith(NAMED_PIPE_PREFIX)) {
32 | return pipeName;
33 | } else {
34 | return NAMED_PIPE_PREFIX + pipeName;
35 | }
36 | }
37 |
38 | static boolean isNamedPipe(String address) {
39 | return address.startsWith(NAMED_PIPE_PREFIX);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/NoOpDirectStatsDClient.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | /**
4 | * A No-Op {@link NonBlockingDirectStatsDClient}, which can be substituted in when metrics are not
5 | * required.
6 | */
7 | public class NoOpDirectStatsDClient extends NoOpStatsDClient implements DirectStatsDClient {
8 | @Override public void recordDistributionValues(String aspect, double[] values, double sampleRate, String... tags) { }
9 |
10 | @Override public void recordDistributionValues(String aspect, long[] values, double sampleRate, String... tags) { }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/NoOpStatsDClient.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | /**
4 | * A No-Op StatsDClient, which can be substituted in when metrics are not
5 | * required.
6 | *
7 | * @author Tom Denley
8 | *
9 | */
10 | public class NoOpStatsDClient implements StatsDClient {
11 |
12 | @Override public void stop() { }
13 |
14 | @Override public void close() { }
15 |
16 | @Override public void count(String aspect, long delta, String... tags) { }
17 |
18 | @Override public void count(String aspect, long delta, double sampleRate, String... tags) { }
19 |
20 | @Override public void count(String aspect, double delta, String... tags) { }
21 |
22 | @Override public void count(String aspect, double delta, double sampleRate, String... tags) { }
23 |
24 | @Override public void countWithTimestamp(String aspect, long delta, long timestamp, String... tags) { }
25 |
26 | @Override public void countWithTimestamp(String aspect, double delta, long timestamp, String... tags) { }
27 |
28 | @Override public void incrementCounter(String aspect, String... tags) { }
29 |
30 | @Override public void incrementCounter(String aspect, double sampleRate, String... tags) { }
31 |
32 | @Override public void increment(String aspect, String... tags) { }
33 |
34 | @Override public void increment(String aspect, double sampleRate, String...tags) { }
35 |
36 | @Override public void decrementCounter(String aspect, String... tags) { }
37 |
38 | @Override public void decrementCounter(String aspect, double sampleRate, String... tags) { }
39 |
40 | @Override public void decrement(String aspect, String... tags) { }
41 |
42 | @Override public void decrement(String aspect, double sampleRate, String... tags) { }
43 |
44 | @Override public void recordGaugeValue(String aspect, double value, String... tags) { }
45 |
46 | @Override public void recordGaugeValue(String aspect, double value, double sampleRate, String... tags) { }
47 |
48 | @Override public void recordGaugeValue(String aspect, long value, String... tags) { }
49 |
50 | @Override public void recordGaugeValue(String aspect, long value, double sampleRate, String... tags) { }
51 |
52 | @Override public void gauge(String aspect, double value, String... tags) { }
53 |
54 | @Override public void gauge(String aspect, double value, double sampleRate, String... tags) { }
55 |
56 | @Override public void gauge(String aspect, long value, String... tags) { }
57 |
58 | @Override public void gauge(String aspect, long value, double sampleRate, String... tags) { }
59 |
60 | @Override public void gaugeWithTimestamp(String aspect, double value, long timestamp, String... tags) { }
61 |
62 | @Override public void gaugeWithTimestamp(String aspect, long value, long timestamp, String... tags) { }
63 |
64 | @Override public void recordExecutionTime(String aspect, long timeInMs, String... tags) { }
65 |
66 | @Override public void recordExecutionTime(String aspect, long timeInMs, double sampleRate, String... tags) { }
67 |
68 | @Override public void time(String aspect, long value, String... tags) { }
69 |
70 | @Override public void time(String aspect, long value, double sampleRate, String... tags) { }
71 |
72 | @Override public void recordHistogramValue(String aspect, double value, String... tags) { }
73 |
74 | @Override public void recordHistogramValue(String aspect, double value, double sampleRate, String... tags) { }
75 |
76 | @Override public void recordHistogramValue(String aspect, long value, String... tags) { }
77 |
78 | @Override public void recordHistogramValue(String aspect, long value, double sampleRate, String... tags) { }
79 |
80 | @Override public void histogram(String aspect, double value, String... tags) { }
81 |
82 | @Override public void histogram(String aspect, double value, double sampleRate, String... tags) { }
83 |
84 | @Override public void histogram(String aspect, long value, String... tags) { }
85 |
86 | @Override public void histogram(String aspect, long value, double sampleRate, String... tags) { }
87 |
88 | @Override public void recordDistributionValue(String aspect, double value, String... tags) { }
89 |
90 | @Override public void recordDistributionValue(String aspect, double value, double sampleRate, String... tags) { }
91 |
92 | @Override public void recordDistributionValue(String aspect, long value, String... tags) { }
93 |
94 | @Override public void recordDistributionValue(String aspect, long value, double sampleRate, String... tags) { }
95 |
96 | @Override public void distribution(String aspect, double value, String... tags) { }
97 |
98 | @Override public void distribution(String aspect, double value, double sampleRate, String... tags) { }
99 |
100 | @Override public void distribution(String aspect, long value, String... tags) { }
101 |
102 | @Override public void distribution(String aspect, long value, double sampleRate, String... tags) { }
103 |
104 | @Override public void recordEvent(final Event event, final String... tags) { }
105 |
106 | @Override public void recordServiceCheckRun(ServiceCheck sc) { }
107 |
108 | @Override public void serviceCheck(ServiceCheck sc) { }
109 |
110 | @Override public void recordSetValue(String aspect, String value, String... tags) { }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/NonBlockingDirectStatsDClient.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | class NonBlockingDirectStatsDClient extends NonBlockingStatsDClient implements DirectStatsDClient {
4 |
5 | public NonBlockingDirectStatsDClient(final NonBlockingStatsDClientBuilder builder) throws StatsDClientException {
6 | super(builder);
7 | }
8 |
9 | @Override
10 | public void recordDistributionValues(String aspect, double[] values, double sampleRate, String... tags) {
11 | if (values != null && values.length > 0) {
12 | sendMetric(new DoublesStatsDMessage(aspect, Message.Type.DISTRIBUTION, values, sampleRate, 0, tags));
13 | }
14 | }
15 |
16 | @Override
17 | public void recordDistributionValues(String aspect, long[] values, double sampleRate, String... tags) {
18 | if (values != null && values.length > 0) {
19 | sendMetric(new LongsStatsDMessage(aspect, Message.Type.DISTRIBUTION, values, sampleRate, 0, tags));
20 | }
21 | }
22 |
23 | abstract class MultiValuedStatsDMessage extends Message {
24 | private final double sampleRate; // NaN for none
25 | private final long timestamp; // zero for none
26 | private int metadataSize = -1; // Cache the size of the metadata, -1 means not calculated yet
27 | private int offset = 0; // The index of the first value that has not been written
28 |
29 | MultiValuedStatsDMessage(String aspect, Message.Type type, String[] tags, double sampleRate, long timestamp) {
30 | super(aspect, type, tags);
31 | this.sampleRate = sampleRate;
32 | this.timestamp = timestamp;
33 | }
34 |
35 | @Override
36 | public final boolean canAggregate() {
37 | return false;
38 | }
39 |
40 | @Override
41 | public final void aggregate(Message message) {
42 | }
43 |
44 | @Override
45 | public final boolean writeTo(StringBuilder builder, int capacity, String containerID) {
46 | int metadataSize = metadataSize(builder, containerID);
47 | writeHeadMetadata(builder);
48 | boolean partialWrite = writeValuesTo(builder, capacity - metadataSize);
49 | writeTailMetadata(builder, containerID);
50 | return partialWrite;
51 |
52 | }
53 |
54 | private int metadataSize(StringBuilder builder, String containerID) {
55 | if (metadataSize == -1) {
56 | final int previousLength = builder.length();
57 | final int previousEncodedLength = Utf8.encodedLength(builder);
58 | writeHeadMetadata(builder);
59 | writeTailMetadata(builder, containerID);
60 | metadataSize = Utf8.encodedLength(builder) - previousEncodedLength;
61 | builder.setLength(previousLength);
62 | }
63 | return metadataSize;
64 | }
65 |
66 | private void writeHeadMetadata(StringBuilder builder) {
67 | builder.append(prefix).append(aspect);
68 | }
69 |
70 | private void writeTailMetadata(StringBuilder builder, String containerID) {
71 | builder.append('|').append(type);
72 | if (!Double.isNaN(sampleRate)) {
73 | builder.append('|').append('@').append(format(SAMPLE_RATE_FORMATTER, sampleRate));
74 | }
75 | if (timestamp != 0) {
76 | builder.append("|T").append(timestamp);
77 | }
78 | tagString(tags, builder);
79 | if (containerID != null && !containerID.isEmpty()) {
80 | builder.append("|c:").append(containerID);
81 | }
82 |
83 | builder.append('\n');
84 | }
85 |
86 | private boolean writeValuesTo(StringBuilder builder, int remainingCapacity) {
87 | if (offset >= lengthOfValues()) {
88 | return false;
89 | }
90 |
91 | int maxLength = builder.length() + remainingCapacity;
92 |
93 | // Add at least one value
94 | builder.append(':');
95 | writeValueTo(builder, offset);
96 | int previousLength = builder.length();
97 |
98 | // Add remaining values up to the max length
99 | for (int i = offset + 1; i < lengthOfValues(); i++) {
100 | builder.append(':');
101 | writeValueTo(builder, i);
102 | if (builder.length() > maxLength) {
103 | builder.setLength(previousLength);
104 | offset = i;
105 | return true;
106 | }
107 | previousLength = builder.length();
108 | }
109 | offset = lengthOfValues();
110 | return false;
111 | }
112 |
113 | protected abstract int lengthOfValues();
114 |
115 | protected abstract void writeValueTo(StringBuilder buffer, int index);
116 | }
117 |
118 | final class LongsStatsDMessage extends MultiValuedStatsDMessage {
119 | private final long[] values;
120 |
121 | LongsStatsDMessage(String aspect, Message.Type type, long[] values, double sampleRate, long timestamp, String[] tags) {
122 | super(aspect, type, tags, sampleRate, timestamp);
123 | this.values = values;
124 | }
125 |
126 | @Override
127 | protected int lengthOfValues() {
128 | return values.length;
129 | }
130 |
131 | @Override
132 | protected void writeValueTo(StringBuilder buffer, int index) {
133 | buffer.append(values[index]);
134 | }
135 | }
136 |
137 | final class DoublesStatsDMessage extends MultiValuedStatsDMessage {
138 | private final double[] values;
139 |
140 | DoublesStatsDMessage(String aspect, Message.Type type, double[] values, double sampleRate, long timestamp,
141 | String[] tags) {
142 | super(aspect, type, tags, sampleRate, timestamp);
143 | this.values = values;
144 | }
145 |
146 | @Override
147 | protected int lengthOfValues() {
148 | return values.length;
149 | }
150 |
151 | @Override
152 | protected void writeValueTo(StringBuilder buffer, int index) {
153 | buffer.append(values[index]);
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/NumericMessage.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 |
4 | public abstract class NumericMessage extends Message {
5 |
6 | protected Number value;
7 |
8 | protected NumericMessage(Message.Type type) {
9 | super(type);
10 | }
11 |
12 | protected NumericMessage(String aspect, Message.Type type, T value, String[] tags) {
13 | super(aspect, type, tags);
14 | this.value = value;
15 | }
16 |
17 |
18 | /**
19 | * Aggregate message.
20 | *
21 | * @param message
22 | * Message to aggregate.
23 | */
24 | @Override
25 | public void aggregate(Message message) {
26 | NumericMessage msg = (NumericMessage)message;
27 | Number value = msg.getValue();
28 | switch (msg.getType()) {
29 | case GAUGE:
30 | setValue(value);
31 | break;
32 | default:
33 | if (value instanceof Double) {
34 | setValue(getValue().doubleValue() + value.doubleValue());
35 | } else if (value instanceof Integer) {
36 | setValue(getValue().intValue() + value.intValue());
37 | } else if (value instanceof Long) {
38 | setValue(getValue().longValue() + value.longValue());
39 | }
40 | }
41 |
42 | return;
43 | }
44 |
45 | /**
46 | * Get underlying message value.
47 | *
48 | * @return returns the value for the Message
49 | */
50 | public Number getValue() {
51 | return this.value;
52 | }
53 |
54 | /**
55 | * Set underlying message value.
56 | *
57 | * @param value the numeric value for the underlying message
58 | */
59 | public void setValue(Number value) {
60 | this.value = value;
61 | }
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/ServiceCheck.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | /**
4 | * A service check model, which is used to format a service check message
5 | * sent to the datadog agent.
6 | */
7 | public class ServiceCheck {
8 |
9 | public enum Status {
10 | OK(0), WARNING(1), CRITICAL(2), UNKNOWN(3);
11 |
12 | private final int val;
13 | Status(final int val) {
14 | this.val = val;
15 | }
16 | }
17 |
18 | private String name;
19 | private String hostname;
20 | private String message;
21 |
22 | private int checkRunId;
23 | private int timestamp;
24 |
25 | private Status status;
26 |
27 | private String[] tags;
28 |
29 | public static Builder builder() {
30 | return new Builder();
31 | }
32 |
33 | public static class Builder {
34 | final ServiceCheck res = new ServiceCheck();
35 |
36 | public Builder withName(final String name) {
37 | res.name = name;
38 | return this;
39 | }
40 |
41 | public Builder withHostname(final String hostname) {
42 | res.hostname = hostname;
43 | return this;
44 | }
45 |
46 | public Builder withMessage(final String message) {
47 | res.message = message;
48 | return this;
49 | }
50 |
51 | public Builder withCheckRunId(final int checkRunId) {
52 | res.checkRunId = checkRunId;
53 | return this;
54 | }
55 |
56 | public Builder withTimestamp(final int timestamp) {
57 | res.timestamp = timestamp;
58 | return this;
59 | }
60 |
61 | public Builder withStatus(final Status status) {
62 | res.status = status;
63 | return this;
64 | }
65 |
66 | public Builder withTags(final String[] tags) {
67 | res.tags = tags;
68 | return this;
69 | }
70 |
71 | public ServiceCheck build() {
72 | return res;
73 | }
74 | }
75 |
76 | private ServiceCheck() {
77 | }
78 |
79 | public String getName() {
80 | return name;
81 | }
82 |
83 | public int getStatus() {
84 | return status.val;
85 | }
86 |
87 | public String getMessage() {
88 | return message;
89 | }
90 |
91 | public String getEscapedMessage() {
92 | return message.replace("\n", "\\n").replace("m:", "m\\:");
93 | }
94 |
95 | public String getHostname() {
96 | return hostname;
97 | }
98 |
99 | public int getTimestamp() {
100 | return timestamp;
101 | }
102 |
103 | public String[] getTags() {
104 | return tags;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/timgroup/statsd/StatsDAggregator.java:
--------------------------------------------------------------------------------
1 | package com.timgroup.statsd;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.Iterator;
6 | import java.util.Map;
7 | import java.util.Timer;
8 | import java.util.TimerTask;
9 |
10 |
11 | public class StatsDAggregator {
12 | public static int DEFAULT_FLUSH_INTERVAL = 2000; // 2s
13 | public static int DEFAULT_SHARDS = 4; // 4 partitions to reduce contention.
14 |
15 | protected final String AGGREGATOR_THREAD_NAME = "statsd-aggregator-thread";
16 | protected final ArrayList