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

Commit 276b1b5

Browse files
authored
Support custom MonitoredResource for Stackdriver Stats Exporter (#870)
1 parent ea590b8 commit 276b1b5

File tree

7 files changed

+203
-27
lines changed

7 files changed

+203
-27
lines changed

exporters/stats/stackdriver/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,43 @@ public class MyMainClass {
6767
}
6868
```
6969

70+
#### Set Monitored Resource for exporter
71+
72+
By default, the Stackdriver Stats Exporter uses [a global Stackdriver monitored resource with no
73+
labels](https://cloud.google.com/monitoring/api/resources#tag_global), and this works fine when you
74+
have only one exporter running. However, if you want to have multiple processes exporting stats for
75+
the same metric concurrently, using the default monitored resource for all exporters will not work.
76+
In this case you need to associate a unique monitored resource with each exporter:
77+
78+
```java
79+
public class MyMainClass {
80+
public static void main(String[] args) {
81+
// A sample AWS EC2 monitored resource.
82+
// This will only work if each EC2 has one process that records stats. If there are multiple
83+
// processes, you'll need an extra label such as pid.
84+
MonitoredResource myResource = MonitoredResource.newBuilder()
85+
.setType("aws_ec2_instance")
86+
.putLabels("instance_id", "instance")
87+
.putLabels("aws_account", "account")
88+
.putLabels("region", "aws:us-west-2")
89+
.build();
90+
91+
// Set a custom MonitoredResource. Please make sure each Stackdriver Stats Exporter has a
92+
// unique MonitoredResource.
93+
StackdriverStatsExporter.createAndRegisterWithProjectIdAndMonitoredResource(
94+
"MyStackdriverProjectId",
95+
Duration.create(10, 0),
96+
myResource);
97+
}
98+
}
99+
```
100+
101+
For a complete list of valid Stackdriver monitored resources, please refer to [Stackdriver
102+
Documentation](https://cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource).
103+
Please also note that although there are a lot of monitored resources available on [Stackdriver](https://cloud.google.com/monitoring/api/resources),
104+
only [a small subset of them](https://cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource)
105+
are compatible with the Opencensus Stackdriver Stats Exporter.
106+
70107
#### Authentication
71108

72109
This exporter uses [google-cloud-java](https://github.com/GoogleCloudPlatform/google-cloud-java),

exporters/stats/stackdriver/src/main/java/io/opencensus/exporter/stats/stackdriver/StackdriverExportUtils.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
import com.google.api.MetricDescriptor;
2828
import com.google.api.MetricDescriptor.MetricKind;
2929
import com.google.api.MonitoredResource;
30-
import com.google.api.client.util.Maps;
3130
import com.google.common.annotations.VisibleForTesting;
3231
import com.google.common.collect.Lists;
32+
import com.google.common.collect.Maps;
3333
import com.google.monitoring.v3.Point;
3434
import com.google.monitoring.v3.TimeInterval;
3535
import com.google.monitoring.v3.TimeSeries;
@@ -137,7 +137,8 @@ static MetricDescriptor.ValueType createValueType(
137137
}
138138

139139
// Convert ViewData to a list of TimeSeries, so that ViewData can be uploaded to Stackdriver.
140-
static List<TimeSeries> createTimeSeriesList(ViewData viewData, String projectId) {
140+
static List<TimeSeries> createTimeSeriesList(
141+
ViewData viewData, MonitoredResource monitoredResource) {
141142
List<TimeSeries> timeSeriesList = Lists.newArrayList();
142143
if (viewData == null) {
143144
return timeSeriesList;
@@ -151,8 +152,7 @@ static List<TimeSeries> createTimeSeriesList(ViewData viewData, String projectId
151152
// Shared fields for all TimeSeries generated from the same ViewData
152153
TimeSeries.Builder shared = TimeSeries.newBuilder();
153154
shared.setMetricKind(createMetricKind(view.getWindow()));
154-
// TODO(songya): add support for custom resource labels.
155-
shared.setResource(MonitoredResource.newBuilder().setType("global"));
155+
shared.setResource(monitoredResource);
156156
shared.setValueType(createValueType(view.getAggregation(), view.getMeasure()));
157157

158158
// Each entry in AggregationMap will be converted into an independent TimeSeries object

exporters/stats/stackdriver/src/main/java/io/opencensus/exporter/stats/stackdriver/StackdriverExporterWorkerThread.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.opencensus.exporter.stats.stackdriver;
1818

1919
import com.google.api.MetricDescriptor;
20+
import com.google.api.MonitoredResource;
2021
import com.google.cloud.monitoring.v3.MetricServiceClient;
2122
import com.google.common.annotations.VisibleForTesting;
2223
import com.google.common.collect.Lists;
@@ -56,18 +57,21 @@ final class StackdriverExporterWorkerThread extends Thread {
5657
private final ProjectName projectName;
5758
private final MetricServiceClient metricServiceClient;
5859
private final ViewManager viewManager;
60+
private final MonitoredResource monitoredResource;
5961
private final Map<View.Name, View> registeredViews = new HashMap<View.Name, View>();
6062

6163
StackdriverExporterWorkerThread(
6264
String projectId,
6365
MetricServiceClient metricServiceClient,
6466
Duration exportInterval,
65-
ViewManager viewManager) {
67+
ViewManager viewManager,
68+
MonitoredResource monitoredResource) {
6669
this.scheduleDelayMillis = toMillis(exportInterval);
6770
this.projectId = projectId;
6871
projectName = ProjectName.newBuilder().setProject(projectId).build();
6972
this.metricServiceClient = metricServiceClient;
7073
this.viewManager = viewManager;
74+
this.monitoredResource = monitoredResource;
7175
setDaemon(true);
7276
setName("ExportWorkerThread");
7377
}
@@ -129,7 +133,8 @@ void export() {
129133
}
130134
List<TimeSeries> timeSeriesList = Lists.newArrayList();
131135
for (ViewData viewData : viewDataList) {
132-
timeSeriesList.addAll(StackdriverExportUtils.createTimeSeriesList(viewData, projectId));
136+
timeSeriesList.addAll(
137+
StackdriverExportUtils.createTimeSeriesList(viewData, monitoredResource));
133138
}
134139

135140
for (List<TimeSeries> batchedTimeSeries :

exporters/stats/stackdriver/src/main/java/io/opencensus/exporter/stats/stackdriver/StackdriverStatsExporter.java

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
package io.opencensus.exporter.stats.stackdriver;
1818

19-
import static com.google.api.client.util.Preconditions.checkArgument;
20-
import static com.google.api.client.util.Preconditions.checkNotNull;
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
import static com.google.common.base.Preconditions.checkNotNull;
2121
import static com.google.common.base.Preconditions.checkState;
2222

23+
import com.google.api.MonitoredResource;
2324
import com.google.api.gax.core.FixedCredentialsProvider;
2425
import com.google.auth.Credentials;
2526
import com.google.auth.oauth2.GoogleCredentials;
@@ -57,21 +58,25 @@ public final class StackdriverStatsExporter {
5758
private static StackdriverStatsExporter exporter = null;
5859

5960
private static final Duration ZERO = Duration.create(0, 0);
61+
private static final MonitoredResource DEFAULT_RESOURCE =
62+
MonitoredResource.newBuilder().setType("global").build();
6063

6164
@VisibleForTesting
6265
StackdriverStatsExporter(
6366
String projectId,
6467
MetricServiceClient metricServiceClient,
6568
Duration exportInterval,
66-
ViewManager viewManager) {
69+
ViewManager viewManager,
70+
MonitoredResource monitoredResource) {
6771
checkArgument(exportInterval.compareTo(ZERO) > 0, "Duration must be positive");
6872
this.workerThread =
6973
new StackdriverExporterWorkerThread(
70-
projectId, metricServiceClient, exportInterval, viewManager);
74+
projectId, metricServiceClient, exportInterval, viewManager, monitoredResource);
7175
}
7276

7377
/**
74-
* Creates a StackdriverStatsExporter for an explicit project ID and using explicit credentials.
78+
* Creates a StackdriverStatsExporter for an explicit project ID and using explicit credentials,
79+
* with default Monitored Resource.
7580
*
7681
* <p>Only one Stackdriver exporter can be created.
7782
*
@@ -85,11 +90,12 @@ public static void createAndRegisterWithCredentialsAndProjectId(
8590
checkNotNull(credentials, "credentials");
8691
checkNotNull(projectId, "projectId");
8792
checkNotNull(exportInterval, "exportInterval");
88-
createInternal(credentials, projectId, exportInterval);
93+
createInternal(credentials, projectId, exportInterval, null);
8994
}
9095

9196
/**
92-
* Creates a Stackdriver Stats exporter for an explicit project ID.
97+
* Creates a Stackdriver Stats exporter for an explicit project ID, with default Monitored
98+
* Resource.
9399
*
94100
* <p>Only one Stackdriver exporter can be created.
95101
*
@@ -111,11 +117,11 @@ public static void createAndRegisterWithProjectId(String projectId, Duration exp
111117
throws IOException {
112118
checkNotNull(projectId, "projectId");
113119
checkNotNull(exportInterval, "exportInterval");
114-
createInternal(null, projectId, exportInterval);
120+
createInternal(null, projectId, exportInterval, null);
115121
}
116122

117123
/**
118-
* Creates a Stackdriver Stats exporter.
124+
* Creates a Stackdriver Stats exporter with default Monitored Resource.
119125
*
120126
* <p>Only one Stackdriver exporter can be created.
121127
*
@@ -135,12 +141,65 @@ public static void createAndRegisterWithProjectId(String projectId, Duration exp
135141
*/
136142
public static void createAndRegister(Duration exportInterval) throws IOException {
137143
checkNotNull(exportInterval, "exportInterval");
138-
createInternal(null, ServiceOptions.getDefaultProjectId(), exportInterval);
144+
createInternal(null, ServiceOptions.getDefaultProjectId(), exportInterval, null);
145+
}
146+
147+
/**
148+
* Creates a Stackdriver Stats exporter with an explicit project ID and a custom Monitored
149+
* Resource.
150+
*
151+
* <p>Only one Stackdriver exporter can be created.
152+
*
153+
* <p>Please refer to cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource
154+
* for a list of valid {@code MonitoredResource}s.
155+
*
156+
* <p>This uses the default application credentials. See {@link
157+
* GoogleCredentials#getApplicationDefault}.
158+
*
159+
* @param projectId the cloud project id.
160+
* @param exportInterval the interval between pushing stats to StackDriver.
161+
* @param monitoredResource the Monitored Resource used by exporter.
162+
* @throws IllegalStateException if a Stackdriver exporter is already created.
163+
*/
164+
public static void createAndRegisterWithProjectIdAndMonitoredResource(
165+
String projectId, Duration exportInterval, MonitoredResource monitoredResource)
166+
throws IOException {
167+
checkNotNull(projectId, "projectId");
168+
checkNotNull(exportInterval, "exportInterval");
169+
checkNotNull(monitoredResource, "monitoredResource");
170+
createInternal(null, projectId, exportInterval, monitoredResource);
171+
}
172+
173+
/**
174+
* Creates a Stackdriver Stats exporter with a custom Monitored Resource.
175+
*
176+
* <p>Only one Stackdriver exporter can be created.
177+
*
178+
* <p>Please refer to cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource
179+
* for a list of valid {@code MonitoredResource}s.
180+
*
181+
* <p>This uses the default application credentials. See {@link
182+
* GoogleCredentials#getApplicationDefault}.
183+
*
184+
* <p>This uses the default project ID configured see {@link ServiceOptions#getDefaultProjectId}.
185+
*
186+
* @param exportInterval the interval between pushing stats to StackDriver.
187+
* @param monitoredResource the Monitored Resource used by exporter.
188+
* @throws IllegalStateException if a Stackdriver exporter is already created.
189+
*/
190+
public static void createAndRegisterWithMonitoredResource(
191+
Duration exportInterval, MonitoredResource monitoredResource) throws IOException {
192+
checkNotNull(exportInterval, "exportInterval");
193+
checkNotNull(monitoredResource, "monitoredResource");
194+
createInternal(null, ServiceOptions.getDefaultProjectId(), exportInterval, monitoredResource);
139195
}
140196

141197
// Use createInternal() (instead of constructor) to enforce singleton.
142198
private static void createInternal(
143-
@Nullable Credentials credentials, String projectId, Duration exportInterval)
199+
@Nullable Credentials credentials,
200+
String projectId,
201+
Duration exportInterval,
202+
@Nullable MonitoredResource monitoredResource)
144203
throws IOException {
145204
synchronized (monitor) {
146205
checkState(exporter == null, "Stackdriver stats exporter is already created.");
@@ -155,9 +214,16 @@ private static void createInternal(
155214
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
156215
.build());
157216
}
217+
if (monitoredResource == null) {
218+
monitoredResource = DEFAULT_RESOURCE;
219+
}
158220
exporter =
159221
new StackdriverStatsExporter(
160-
projectId, metricServiceClient, exportInterval, Stats.getViewManager());
222+
projectId,
223+
metricServiceClient,
224+
exportInterval,
225+
Stats.getViewManager(),
226+
monitoredResource);
161227
exporter.workerThread.start();
162228
}
163229
}

exporters/stats/stackdriver/src/test/java/io/opencensus/exporter/stats/stackdriver/StackdriverExportUtilsTest.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ public class StackdriverExportUtilsTest {
9292
private static final Mean MEAN = Mean.create();
9393
private static final Distribution DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES);
9494
private static final String PROJECT_ID = "id";
95+
private static final MonitoredResource DEFAULT_RESOURCE =
96+
MonitoredResource.newBuilder().setType("global").build();
9597

9698
@Test
9799
public void testConstant() {
@@ -366,7 +368,7 @@ public void createTimeSeriesList_cumulative() {
366368
CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000));
367369
ViewData viewData = ViewData.create(view, aggregationMap, cumulativeData);
368370
List<TimeSeries> timeSeriesList =
369-
StackdriverExportUtils.createTimeSeriesList(viewData, PROJECT_ID);
371+
StackdriverExportUtils.createTimeSeriesList(viewData, DEFAULT_RESOURCE);
370372
assertThat(timeSeriesList).hasSize(2);
371373
TimeSeries expected1 =
372374
TimeSeries.newBuilder()
@@ -407,6 +409,37 @@ public void createTimeSeriesList_interval() {
407409
DistributionData.create(-1, 1, -1, -1, 0, Arrays.asList(1L, 0L, 0L, 0L, 0L)));
408410
ViewData viewData =
409411
ViewData.create(view, aggregationMap, IntervalData.create(Timestamp.fromMillis(2000)));
410-
assertThat(StackdriverExportUtils.createTimeSeriesList(viewData, PROJECT_ID)).isEmpty();
412+
assertThat(StackdriverExportUtils.createTimeSeriesList(viewData, DEFAULT_RESOURCE)).isEmpty();
413+
}
414+
415+
@Test
416+
public void createTimeSeriesList_withCustomMonitoredResource() {
417+
MonitoredResource resource =
418+
MonitoredResource.newBuilder().setType("global").putLabels("key", "value").build();
419+
View view =
420+
View.create(
421+
Name.create(VIEW_NAME),
422+
VIEW_DESCRIPTION,
423+
MEASURE_DOUBLE,
424+
SUM,
425+
Arrays.asList(KEY),
426+
CUMULATIVE);
427+
SumDataDouble sumData = SumDataDouble.create(55.5);
428+
Map<List<TagValue>, SumDataDouble> aggregationMap =
429+
ImmutableMap.of(Arrays.asList(VALUE_1), sumData);
430+
CumulativeData cumulativeData =
431+
CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000));
432+
ViewData viewData = ViewData.create(view, aggregationMap, cumulativeData);
433+
List<TimeSeries> timeSeriesList =
434+
StackdriverExportUtils.createTimeSeriesList(viewData, resource);
435+
assertThat(timeSeriesList)
436+
.containsExactly(
437+
TimeSeries.newBuilder()
438+
.setMetricKind(MetricKind.CUMULATIVE)
439+
.setValueType(MetricDescriptor.ValueType.DOUBLE)
440+
.setMetric(StackdriverExportUtils.createMetric(view, Arrays.asList(VALUE_1)))
441+
.setResource(resource)
442+
.addPoints(StackdriverExportUtils.createPoint(sumData, cumulativeData, SUM))
443+
.build());
411444
}
412445
}

0 commit comments

Comments
 (0)