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

Commit ebd1dfd

Browse files
authored
Add an end-to-end test for the Stackdriver stats exporter. (#33)
This writes to production Stackdriver and then uses Stackdriver's data retrieval API to check for successful export. It must be configured to communicate with a monitoring-enabled GCP project and is subject to network/Stackdriver outages, so it is marked manual.
1 parent 5fe0537 commit ebd1dfd

File tree

5 files changed

+324
-8
lines changed

5 files changed

+324
-8
lines changed

ci.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ fi
4949
# Exclude tests tagged "noci". Tests marked "manual" are already excluded from
5050
# wildcard queries.
5151
tests=$(bazel query -k --noshow_progress \
52-
"kind(test, rdeps(//..., set(${files[*]}))) except attr('tags', 'noci', //...)" \
52+
"kind(test, rdeps(//..., set(${files[*]}))) \
53+
except attr('tags', 'noci', //...) \
54+
except attr('tags', 'manual', //...)" \
5355
| grep -v :_)
5456
if [[ ! -z $tests ]]; then
5557
echo "Running tests"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# End-to-end tests for stats exporter libraries.
2+
#
3+
# Copyright 2018, OpenCensus Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
licenses(["notice"]) # Apache License 2.0
18+
19+
package(default_visibility = ["//visibility:private"])
20+
21+
cc_test(
22+
name = "stackdriver_e2e_test",
23+
srcs = ["stackdriver_e2e_test.cc"],
24+
tags = [
25+
"manual",
26+
],
27+
deps = [
28+
"//google/monitoring/v3:common",
29+
"//google/monitoring/v3:metric",
30+
"//google/monitoring/v3:metric_service",
31+
"//opencensus/exporters/stats:stackdriver_exporter",
32+
"//opencensus/exporters/stats:stackdriver_utils",
33+
"//opencensus/exporters/stats:test_utils",
34+
"//opencensus/stats",
35+
"@com_github_grpc_grpc//:grpc++",
36+
"@com_google_absl//absl/strings",
37+
"@com_google_absl//absl/time",
38+
"@com_google_googletest//:gtest_main",
39+
],
40+
)
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
// Copyright 2018, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <cstdlib>
16+
#include <iostream>
17+
18+
#include "absl/strings/str_cat.h"
19+
#include "absl/strings/string_view.h"
20+
#include "absl/time/clock.h"
21+
#include "absl/time/time.h"
22+
#include "gmock/gmock.h"
23+
#include "google/monitoring/v3/common.pb.h"
24+
#include "google/monitoring/v3/metric_service.grpc.pb.h"
25+
#include "google/protobuf/empty.pb.h"
26+
#include "gtest/gtest.h"
27+
#include "include/grpc++/grpc++.h"
28+
#include "opencensus/exporters/stats/internal/stackdriver_utils.h"
29+
#include "opencensus/exporters/stats/internal/testing/time_series_matcher.h"
30+
#include "opencensus/exporters/stats/stackdriver_exporter.h"
31+
#include "opencensus/stats/stats.h"
32+
33+
namespace opencensus {
34+
namespace exporters {
35+
namespace stats {
36+
namespace {
37+
38+
// This is a true end-to-end test for the stackdriver exporter, connecting to
39+
// production Stackdriver. As such, it is subject to failures in networking or
40+
// the Stackdriver backend; it also cannot be run multiple times simultaneously
41+
// under the same Cloud project.
42+
//
43+
// See comments in stackdriver_exporter.h regarding setting up authentication.
44+
// Additionally, it requires the environment variable STACKDRIVER_PROJECT_ID to
45+
// be set to the project id corresponding to the GOOGLE_APPLICATION_CREDENTIALS,
46+
// and for the account specified by the credentials to have the "Monitoring
47+
// Viewer" permission.
48+
49+
constexpr char kGoogleStackdriverStatsAddress[] = "monitoring.googleapis.com";
50+
51+
constexpr char kTestMeasureName[] = "opencensus.io/TestMeasure";
52+
opencensus::stats::MeasureDouble TestMeasure() {
53+
static const opencensus::stats::MeasureDouble foo_usage =
54+
opencensus::stats::MeasureRegistry::RegisterDouble(
55+
kTestMeasureName, "1{test units}", "Test measure.");
56+
return foo_usage;
57+
}
58+
59+
class StackdriverE2eTest : public ::testing::Test {
60+
protected:
61+
static void SetUpTestCase() {
62+
TestMeasure();
63+
64+
if (project_id_.empty() || credentials_.empty()) {
65+
std::cerr << "STACKDRIVER_PROJECT_ID or GOOGLE_APPLICATION_CREDENTIALS "
66+
"unset.\n";
67+
std::abort();
68+
}
69+
StackdriverExporter::Register(project_id_, "test_task");
70+
}
71+
72+
// Retrieves data exported under 'descriptor'.
73+
std::vector<google::monitoring::v3::TimeSeries> RetrieveData(
74+
const opencensus::stats::ViewDescriptor& descriptor);
75+
76+
// Cleans up by deleting the metric the Stackdriver exporter should have
77+
// created for 'descriptor'.
78+
void DeleteMetric(const opencensus::stats::ViewDescriptor& descriptor);
79+
80+
static const absl::string_view project_id_;
81+
static const absl::string_view credentials_;
82+
// A prefix to add to view names because a newly-created Stackdriver metric
83+
// may resurrect data from deleted metrics with the same name.
84+
std::string prefix_ = absl::StrCat(absl::ToUnixMillis(absl::Now()));
85+
86+
const std::unique_ptr<google::monitoring::v3::MetricService::Stub> stub_ =
87+
google::monitoring::v3::MetricService::NewStub(::grpc::CreateChannel(
88+
kGoogleStackdriverStatsAddress, ::grpc::GoogleDefaultCredentials()));
89+
};
90+
91+
const absl::string_view StackdriverE2eTest::project_id_ =
92+
std::getenv("STACKDRIVER_PROJECT_ID");
93+
const absl::string_view StackdriverE2eTest::credentials_ =
94+
std::getenv("GOOGLE_APPLICATION_CREDENTIALS");
95+
96+
std::vector<google::monitoring::v3::TimeSeries>
97+
StackdriverE2eTest::RetrieveData(
98+
const opencensus::stats::ViewDescriptor& descriptor) {
99+
std::vector<google::monitoring::v3::TimeSeries> data;
100+
google::monitoring::v3::ListTimeSeriesRequest request;
101+
request.set_name(absl::StrCat("projects/", project_id_));
102+
request.set_filter(
103+
absl::StrCat("metric.type = \"custom.googleapis.com/opencensus/",
104+
descriptor.name(), "\""));
105+
SetTimestamp(absl::Now() - absl::Hours(1),
106+
request.mutable_interval()->mutable_start_time());
107+
SetTimestamp(absl::Now() + absl::Hours(1),
108+
request.mutable_interval()->mutable_end_time());
109+
110+
while (true) {
111+
google::monitoring::v3::ListTimeSeriesResponse response;
112+
::grpc::ClientContext context;
113+
::grpc::Status status = stub_->ListTimeSeries(&context, request, &response);
114+
if (!status.ok()) {
115+
std::cerr << "ListTimeSeries error: " << status.error_message() << "\n";
116+
// This may mean that the data has not propagated through Stackdriver
117+
// yet.
118+
std::cout << "Waiting and retrying.\n";
119+
absl::SleepFor(absl::Seconds(5));
120+
continue;
121+
}
122+
123+
// If there are too many response Stackdriver returns them in multiple
124+
// pages.
125+
for (const auto& ts : response.time_series()) {
126+
data.push_back(ts);
127+
}
128+
if (response.next_page_token().empty()) {
129+
break;
130+
} else {
131+
request.set_page_token(response.next_page_token());
132+
}
133+
}
134+
return data;
135+
}
136+
137+
void StackdriverE2eTest::DeleteMetric(
138+
const opencensus::stats::ViewDescriptor& descriptor) {
139+
// Remove from the exporter before deleting from Stackdriver so that the
140+
// exporter does not recreate it.
141+
opencensus::stats::StatsExporter::RemoveView(descriptor.name());
142+
143+
google::monitoring::v3::DeleteMetricDescriptorRequest request;
144+
request.set_name(
145+
absl::StrCat("projects/", project_id_,
146+
"/metricDescriptors/custom.googleapis.com/opencensus/",
147+
descriptor.name()));
148+
::grpc::ClientContext context;
149+
google::protobuf::Empty response;
150+
::grpc::Status status =
151+
stub_->DeleteMetricDescriptor(&context, request, &response);
152+
EXPECT_TRUE(status.ok());
153+
}
154+
155+
TEST_F(StackdriverE2eTest, OneView) {
156+
// Make sure that the simple case works.
157+
const auto view_descriptor =
158+
opencensus::stats::ViewDescriptor()
159+
.set_name(absl::StrCat(
160+
"opencensus.io/Test/TestMeasure-sum-cumulative-key1-key2-",
161+
prefix_))
162+
.set_measure(kTestMeasureName)
163+
.set_aggregation(::opencensus::stats::Aggregation::Sum())
164+
.set_aggregation_window(
165+
::opencensus::stats::AggregationWindow::Cumulative())
166+
.add_column("key1")
167+
.add_column("key2")
168+
.set_description(
169+
"Cumulative sum of opencensus.io/TestMeasure broken down "
170+
"by 'key1' and 'key2'.");
171+
opencensus::stats::StatsExporter::AddView(view_descriptor);
172+
173+
opencensus::stats::Record({{TestMeasure(), 1.0}},
174+
{{"key1", "v11"}, {"key2", "v21"}});
175+
opencensus::stats::Record({{TestMeasure(), 2.0}},
176+
{{"key1", "v11"}, {"key2", "v22"}});
177+
178+
std::cout << "Waiting for data to propagate.\n";
179+
absl::SleepFor(absl::Seconds(40));
180+
EXPECT_THAT(RetrieveData(view_descriptor),
181+
::testing::UnorderedElementsAre(
182+
testing::TimeSeriesDouble({{"opencensus_task", "test_task"},
183+
{"key1", "v11"},
184+
{"key2", "v21"}},
185+
1.0),
186+
testing::TimeSeriesDouble({{"opencensus_task", "test_task"},
187+
{"key1", "v11"},
188+
{"key2", "v22"}},
189+
2.0)));
190+
191+
DeleteMetric(view_descriptor);
192+
}
193+
194+
TEST_F(StackdriverE2eTest, LargeTest) {
195+
const auto count_descriptor =
196+
opencensus::stats::ViewDescriptor()
197+
.set_name(absl::StrCat(
198+
"opencensus.io/Test/TestMeasure-count-cumulative-key1-key2-",
199+
prefix_))
200+
.set_measure(kTestMeasureName)
201+
.set_aggregation(::opencensus::stats::Aggregation::Count())
202+
.set_aggregation_window(
203+
::opencensus::stats::AggregationWindow::Cumulative())
204+
.add_column("key1")
205+
.add_column("key2")
206+
.set_description(
207+
"Cumulative count of opencensus.io/TestMeasure broken down "
208+
"by 'key1' and 'key2'.");
209+
opencensus::stats::StatsExporter::AddView(count_descriptor);
210+
211+
const auto sum_descriptor =
212+
opencensus::stats::ViewDescriptor()
213+
.set_name(absl::StrCat(
214+
"opencensus.io/Test/TestMeasure-sum-cumulative-key1-key2-",
215+
prefix_))
216+
.set_measure(kTestMeasureName)
217+
.set_aggregation(::opencensus::stats::Aggregation::Sum())
218+
.set_aggregation_window(
219+
::opencensus::stats::AggregationWindow::Cumulative())
220+
.add_column("key1")
221+
.add_column("key2")
222+
.set_description(
223+
"Cumulative sum of opencensus.io/TestMeasure broken down "
224+
"by 'key1' and 'key2'.");
225+
opencensus::stats::StatsExporter::AddView(sum_descriptor);
226+
227+
std::vector<::testing::Matcher<google::monitoring::v3::TimeSeries>>
228+
sum_matchers;
229+
std::vector<::testing::Matcher<google::monitoring::v3::TimeSeries>>
230+
count_matchers;
231+
232+
// The number of values to record for each tag--total metric cardinality will
233+
// be tag_values^2.
234+
const int tag_values = 10;
235+
for (int i = 0; i < tag_values; ++i) {
236+
for (int j = 0; j < tag_values; ++j) {
237+
const std::string tag1 = absl::StrCat("v1", i);
238+
const std::string tag2 = absl::StrCat("v1", j);
239+
const double value = i * j;
240+
opencensus::stats::Record({{TestMeasure(), value}},
241+
{{"key1", tag1}, {"key2", tag2}});
242+
sum_matchers.push_back(testing::TimeSeriesDouble(
243+
{{"opencensus_task", "test_task"}, {"key1", tag1}, {"key2", tag2}},
244+
value));
245+
count_matchers.push_back(testing::TimeSeriesInt(
246+
{{"opencensus_task", "test_task"}, {"key1", tag1}, {"key2", tag2}},
247+
1));
248+
}
249+
}
250+
251+
std::cout << "Waiting for data to propagate.\n";
252+
absl::SleepFor(absl::Seconds(40));
253+
254+
const auto count_data = RetrieveData(count_descriptor);
255+
const auto sum_data = RetrieveData(sum_descriptor);
256+
ASSERT_EQ(tag_values * tag_values, count_data.size());
257+
ASSERT_EQ(tag_values * tag_values, sum_data.size());
258+
259+
EXPECT_THAT(RetrieveData(count_descriptor),
260+
::testing::UnorderedElementsAreArray(count_matchers));
261+
EXPECT_THAT(RetrieveData(sum_descriptor),
262+
::testing::UnorderedElementsAreArray(sum_matchers));
263+
264+
DeleteMetric(count_descriptor);
265+
DeleteMetric(sum_descriptor);
266+
}
267+
268+
} // namespace
269+
} // namespace stats
270+
} // namespace exporters
271+
} // namespace opencensus

opencensus/exporters/stats/internal/stackdriver_utils.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,6 @@ google::api::MetricDescriptor::ValueType GetValueType(
9494
}
9595
}
9696

97-
void SetTimestamp(absl::Time time, google::protobuf::Timestamp* proto) {
98-
const int64_t seconds = absl::ToUnixSeconds(time);
99-
proto->set_seconds(seconds);
100-
proto->set_nanos(
101-
absl::ToInt64Nanoseconds(time - absl::FromUnixSeconds(seconds)));
102-
}
103-
10497
// Overloaded function for converting ViewData value types to Points. The
10598
// ValueType is needed because Sum aggregation with an int64 measure returns
10699
// doubles but we want to export int64s for future compatibility.
@@ -207,6 +200,13 @@ std::vector<google::monitoring::v3::TimeSeries> MakeTimeSeries(
207200
}
208201
}
209202

203+
void SetTimestamp(absl::Time time, google::protobuf::Timestamp* proto) {
204+
const int64_t seconds = absl::ToUnixSeconds(time);
205+
proto->set_seconds(seconds);
206+
proto->set_nanos(
207+
absl::ToInt64Nanoseconds(time - absl::FromUnixSeconds(seconds)));
208+
}
209+
210210
} // namespace stats
211211
} // namespace exporters
212212
} // namespace opencensus

opencensus/exporters/stats/internal/stackdriver_utils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "absl/strings/string_view.h"
2121
#include "google/api/metric.pb.h"
2222
#include "google/monitoring/v3/metric.pb.h"
23+
#include "google/protobuf/timestamp.pb.h"
2324
#include "opencensus/stats/stats.h"
2425

2526
namespace opencensus {
@@ -38,6 +39,8 @@ std::vector<google::monitoring::v3::TimeSeries> MakeTimeSeries(
3839
const opencensus::stats::ViewDescriptor& view_descriptor,
3940
const opencensus::stats::ViewData& data, absl::string_view opencensus_task);
4041

42+
void SetTimestamp(absl::Time time, google::protobuf::Timestamp* proto);
43+
4144
} // namespace stats
4245
} // namespace exporters
4346
} // namespace opencensus

0 commit comments

Comments
 (0)