Skip to content
This repository was archived by the owner on Dec 23, 2023. It is now read-only.

Commit 64d0583

Browse files
mpetazzonisongy23
authored andcommitted
SignalFx stats exporter (#903)
* SignalFx stats exporter This commit implements a stats exporter that sends stats as metrics to SignalFx (https://signalfx.com). It is modeled on the Stackdriver stats exporter in that it uses a background worker thread that periodically inspects all the views and constructs datapoints to send to SignalFx for their corresponding metric timeseries (identified by the view name as metric name, and tag key/value pairs as dimensions). * Review feedback (squash me) * More review feedback (squash me) * Use a configuration builder pattern (squash me) * Fixes for checker framework compliance (squash me) * Review feedback (squash me) * Remove support for AggregationWindow.Interval (squash me)
1 parent 7bfe925 commit 64d0583

15 files changed

+1293
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ runtime 'io.opencensus:opencensus-impl:0.10.1'
111111

112112
#### Stats exporters
113113
* [Stackdriver][StatsExporterStackdriver]
114+
* [SignalFx][StatsExporterSignalFx]
114115

115116
### How to setup debugging Z-Pages?
116117

@@ -133,3 +134,4 @@ see this [link](https://github.com/census-instrumentation/opencensus-java/tree/m
133134
[TraceExporterStackdriver]: https://github.com/census-instrumentation/opencensus-java/tree/master/exporters/trace/stackdriver#quickstart
134135
[TraceExporterZipkin]: https://github.com/census-instrumentation/opencensus-java/tree/master/exporters/trace/zipkin#quickstart
135136
[StatsExporterStackdriver]: https://github.com/census-instrumentation/opencensus-java/tree/master/exporters/stats/stackdriver#quickstart
137+
[StatsExporterSignalFx]: https://github.com/census-instrumentation/opencensus-java/tree/master/exporters/stats/signalfx#quickstart

all/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def subprojects = [
2424
project(':opencensus-exporter-trace-logging'),
2525
project(':opencensus-exporter-trace-stackdriver'),
2626
project(':opencensus-exporter-trace-zipkin'),
27+
project(':opencensus-exporter-stats-signalfx'),
2728
project(':opencensus-exporter-stats-stackdriver'),
2829
]
2930

@@ -38,6 +39,7 @@ def subprojects_javadoc = [
3839
project(':opencensus-exporter-trace-logging'),
3940
project(':opencensus-exporter-trace-stackdriver'),
4041
project(':opencensus-exporter-trace-zipkin'),
42+
project(':opencensus-exporter-stats-signalfx'),
4143
project(':opencensus-exporter-stats-stackdriver'),
4244
]
4345

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ subprojects {
138138
guavaVersion = '19.0'
139139
googleAuthVersion = '0.9.0'
140140
googleCloudVersion = '0.30.0-beta'
141+
signalfxVersion = '0.0.39'
141142
zipkinReporterVersion = '2.0.0'
142143

143144
libraries = [
@@ -157,6 +158,7 @@ subprojects {
157158
grpc_core: "io.grpc:grpc-core:${grpcVersion}",
158159
guava: "com.google.guava:guava:${guavaVersion}",
159160
jsr305: "com.google.code.findbugs:jsr305:${findBugsVersion}",
161+
signalfx_java: "com.signalfx.public:signalfx-java:${signalfxVersion}",
160162

161163
// Test dependencies.
162164
guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}",
@@ -319,6 +321,7 @@ subprojects {
319321
'opencensus-contrib-grpc-metrics',
320322
'opencensus-contrib-grpc-util',
321323
'opencensus-contrib-zpages',
324+
'opencensus-exporter-stats-signalfx',
322325
'opencensus-exporter-stats-stackdriver',
323326
'opencensus-exporter-trace-logging',
324327
'opencensus-exporter-trace-stackdriver',

exporters/stats/signalfx/README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# OpenCensus SignalFx Stats Exporter
2+
3+
The _OpenCensus SignalFx Stats Exporter_ is a stats exporter that
4+
exports data to [SignalFx](https://signalfx.com), a real-time monitoring
5+
solution for cloud and distributed applications. SignalFx ingests that
6+
data and offers various visualizations on charts, dashboards and service
7+
maps, as well as real-time anomaly detection.
8+
9+
## Quickstart
10+
11+
### Prerequisites
12+
13+
To use this exporter, you must have a [SignalFx](https://signalfx.com)
14+
account and corresponding [data ingest
15+
token](https://docs.signalfx.com/en/latest/admin-guide/tokens.html).
16+
17+
#### Java versions
18+
19+
This exporter requires Java 7 or above.
20+
21+
### Add the dependencies to your project
22+
23+
For Maven add to your `pom.xml`:
24+
25+
```xml
26+
<dependencies>
27+
<dependency>
28+
<groupId>io.opencensus</groupId>
29+
<artifactId>opencensus-api</artifactId>
30+
<version>0.10.1</version>
31+
</dependency>
32+
<dependency>
33+
<groupId>io.opencensus</groupId>
34+
<artifactId>opencensus-exporter-stats-signalfx</artifactId>
35+
<version>0.10.1</version>
36+
</dependency>
37+
<dependency>
38+
<groupId>io.opencensus</groupId>
39+
<artifactId>opencensus-impl</artifactId>
40+
<version>0.10.1</version>
41+
<scope>runtime</scope>
42+
</dependency>
43+
</dependencies>
44+
```
45+
46+
For Gradle add to your dependencies:
47+
48+
```
49+
compile 'io.opencensus:opencensus-api:0.10.1'
50+
compile 'io.opencensus:opencensus-exporter-stats-signalfx:0.10.1'
51+
runtime 'io.opencensus:opencensus-impl:0.10.1'
52+
```
53+
54+
### Register the exporter
55+
56+
```java
57+
public class MyMainClass {
58+
public static void main(String[] args) {
59+
// SignalFx token is read from Java system properties.
60+
// Stats will be reported every second by default.
61+
SignalFxStatsExporter.create();
62+
}
63+
}
64+
```
65+
66+
If you want to pass in the token yourself, or set a different reporting
67+
interval, use:
68+
69+
```java
70+
// Use token "your_signalfx_token" and report every 5 seconds.
71+
SignalFxStatsExporter.create(
72+
SignalFxStatsExporter.DEFAULT_SIGNALFX_ENDPOINT,
73+
"your_signalfx_token",
74+
Duration.create(5, 0));
75+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
description = 'OpenCensus SignalFx Stats Exporter'
2+
3+
[compileJava, compileTestJava].each() {
4+
it.sourceCompatibility = 1.7
5+
it.targetCompatibility = 1.7
6+
}
7+
8+
dependencies {
9+
compileOnly libraries.auto_value
10+
11+
compile project(':opencensus-api'),
12+
libraries.signalfx_java
13+
14+
testCompile project(':opencensus-api')
15+
16+
signature "org.codehaus.mojo.signature:java17:+@signature"
17+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2017, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opencensus.exporter.stats.signalfx;
18+
19+
import com.signalfx.endpoint.SignalFxEndpoint;
20+
import com.signalfx.metrics.auth.StaticAuthToken;
21+
import com.signalfx.metrics.connection.HttpDataPointProtobufReceiverFactory;
22+
import com.signalfx.metrics.connection.HttpEventProtobufReceiverFactory;
23+
import com.signalfx.metrics.errorhandler.OnSendErrorHandler;
24+
import com.signalfx.metrics.flush.AggregateMetricSender;
25+
import java.net.URI;
26+
import java.util.Collections;
27+
28+
/** Interface for creators of {@link AggregateMetricSender}. */
29+
interface SignalFxMetricsSenderFactory {
30+
31+
/**
32+
* Creates a new SignalFx metrics sender instance.
33+
*
34+
* @param endpoint The SignalFx ingest endpoint URL.
35+
* @param token The SignalFx ingest token.
36+
* @param errorHandler An {@link OnSendErrorHandler} through which errors when sending data to
37+
* SignalFx will be communicated.
38+
* @return The created {@link AggregateMetricSender} instance.
39+
*/
40+
AggregateMetricSender create(URI endpoint, String token, OnSendErrorHandler errorHandler);
41+
42+
/** The default, concrete implementation of this interface. */
43+
SignalFxMetricsSenderFactory DEFAULT =
44+
new SignalFxMetricsSenderFactory() {
45+
@Override
46+
@SuppressWarnings("nullness")
47+
public AggregateMetricSender create(
48+
URI endpoint, String token, OnSendErrorHandler errorHandler) {
49+
SignalFxEndpoint sfx =
50+
new SignalFxEndpoint(endpoint.getScheme(), endpoint.getHost(), endpoint.getPort());
51+
return new AggregateMetricSender(
52+
null,
53+
new HttpDataPointProtobufReceiverFactory(sfx).setVersion(2),
54+
new HttpEventProtobufReceiverFactory(sfx),
55+
new StaticAuthToken(token),
56+
Collections.singleton(errorHandler));
57+
}
58+
};
59+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2017, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opencensus.exporter.stats.signalfx;
18+
19+
import com.google.common.annotations.VisibleForTesting;
20+
import com.google.common.base.Preconditions;
21+
import com.google.common.base.Strings;
22+
import com.signalfx.metrics.protobuf.SignalFxProtocolBuffers.DataPoint;
23+
import com.signalfx.metrics.protobuf.SignalFxProtocolBuffers.Datum;
24+
import com.signalfx.metrics.protobuf.SignalFxProtocolBuffers.Dimension;
25+
import com.signalfx.metrics.protobuf.SignalFxProtocolBuffers.MetricType;
26+
import io.opencensus.common.Function;
27+
import io.opencensus.common.Functions;
28+
import io.opencensus.stats.Aggregation;
29+
import io.opencensus.stats.AggregationData;
30+
import io.opencensus.stats.AggregationData.CountData;
31+
import io.opencensus.stats.AggregationData.DistributionData;
32+
import io.opencensus.stats.AggregationData.MeanData;
33+
import io.opencensus.stats.AggregationData.SumDataDouble;
34+
import io.opencensus.stats.AggregationData.SumDataLong;
35+
import io.opencensus.stats.View;
36+
import io.opencensus.stats.View.AggregationWindow;
37+
import io.opencensus.stats.ViewData;
38+
import io.opencensus.tags.TagKey;
39+
import io.opencensus.tags.TagValue;
40+
import java.util.ArrayList;
41+
import java.util.Collections;
42+
import java.util.List;
43+
import java.util.ListIterator;
44+
import java.util.Map;
45+
46+
/*>>>
47+
import org.checkerframework.checker.nullness.qual.Nullable;
48+
*/
49+
50+
/** Adapter for a {@code ViewData}'s contents into SignalFx datapoints. */
51+
final class SignalFxSessionAdaptor {
52+
53+
private SignalFxSessionAdaptor() {}
54+
55+
/**
56+
* Converts the given view data into datapoints that can be sent to SignalFx.
57+
*
58+
* <p>The view name is used as the metric name, and the aggregation type and aggregation window
59+
* type determine the metric type.
60+
*
61+
* @param data The {@link ViewData} containing the aggregation data of each combination of tag
62+
* values.
63+
* @return A list of datapoints for the corresponding metric timeseries of this view's metric.
64+
*/
65+
static List<DataPoint> adapt(ViewData data) {
66+
View view = data.getView();
67+
List<TagKey> keys = view.getColumns();
68+
69+
MetricType metricType = getMetricTypeForAggregation(view.getAggregation(), view.getWindow());
70+
if (metricType == null) {
71+
return Collections.emptyList();
72+
}
73+
74+
List<DataPoint> datapoints = new ArrayList<>(data.getAggregationMap().size());
75+
for (Map.Entry<List<TagValue>, AggregationData> entry : data.getAggregationMap().entrySet()) {
76+
datapoints.add(
77+
DataPoint.newBuilder()
78+
.setMetric(view.getName().asString())
79+
.setMetricType(metricType)
80+
.addAllDimensions(createDimensions(keys, entry.getKey()))
81+
.setValue(createDatum(entry.getValue()))
82+
.build());
83+
}
84+
return datapoints;
85+
}
86+
87+
@VisibleForTesting
88+
@javax.annotation.Nullable
89+
static MetricType getMetricTypeForAggregation(Aggregation aggregation, AggregationWindow window) {
90+
if (aggregation instanceof Aggregation.Mean) {
91+
return MetricType.GAUGE;
92+
} else if (aggregation instanceof Aggregation.Count || aggregation instanceof Aggregation.Sum) {
93+
if (window instanceof AggregationWindow.Cumulative) {
94+
return MetricType.CUMULATIVE_COUNTER;
95+
}
96+
// TODO(mpetazzoni): support incremental counters when AggregationWindow.Interval is ready
97+
}
98+
99+
// TODO(mpetazzoni): add support for histograms (Aggregation.Distribution).
100+
return null;
101+
}
102+
103+
@VisibleForTesting
104+
static Iterable<Dimension> createDimensions(List<TagKey> keys, List<TagValue> values) {
105+
Preconditions.checkArgument(
106+
keys.size() == values.size(), "TagKeys and TagValues don't have the same size.");
107+
List<Dimension> dimensions = new ArrayList<>(keys.size());
108+
for (ListIterator<TagKey> it = keys.listIterator(); it.hasNext(); ) {
109+
TagKey key = it.next();
110+
TagValue value = values.get(it.previousIndex());
111+
if (value == null || Strings.isNullOrEmpty(value.asString())) {
112+
continue;
113+
}
114+
dimensions.add(createDimension(key, value));
115+
}
116+
return dimensions;
117+
}
118+
119+
@VisibleForTesting
120+
static Dimension createDimension(TagKey key, TagValue value) {
121+
return Dimension.newBuilder().setKey(key.getName()).setValue(value.asString()).build();
122+
}
123+
124+
@VisibleForTesting
125+
static Datum createDatum(AggregationData data) {
126+
final Datum.Builder builder = Datum.newBuilder();
127+
data.match(
128+
new Function<SumDataDouble, Void>() {
129+
@Override
130+
public Void apply(SumDataDouble arg) {
131+
builder.setDoubleValue(arg.getSum());
132+
return null;
133+
}
134+
},
135+
new Function<SumDataLong, Void>() {
136+
@Override
137+
public Void apply(SumDataLong arg) {
138+
builder.setIntValue(arg.getSum());
139+
return null;
140+
}
141+
},
142+
new Function<CountData, Void>() {
143+
@Override
144+
public Void apply(CountData arg) {
145+
builder.setIntValue(arg.getCount());
146+
return null;
147+
}
148+
},
149+
new Function<MeanData, Void>() {
150+
@Override
151+
public Void apply(MeanData arg) {
152+
builder.setDoubleValue(arg.getMean());
153+
return null;
154+
}
155+
},
156+
new Function<DistributionData, Void>() {
157+
@Override
158+
public Void apply(DistributionData arg) {
159+
// TODO(mpetazzoni): add histogram support.
160+
throw new IllegalArgumentException("Distribution aggregations are not supported");
161+
}
162+
},
163+
Functions.</*@Nullable*/ Void>throwIllegalArgumentException());
164+
return builder.build();
165+
}
166+
}

0 commit comments

Comments
 (0)