1 // Copyright 2020 Google LLC
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 <fstream>
16 #include <memory>
17 #include <string>
18
19 #include "gtest/gtest.h"
20 #include "absl/strings/str_cat.h"
21 #include "absl/time/clock.h"
22 #include "absl/time/time.h"
23 #include "fcp/base/monitoring.h"
24 #include "fcp/base/platform.h"
25 #include "fcp/base/scheduler.h"
26 #include "fcp/testing/testing.h"
27 #include "fcp/tracing/scoped_tracing_recorder.h"
28 #include "fcp/tracing/test/tracing_schema.h"
29 #include "fcp/tracing/text_tracing_recorder.h"
30 #include "re2/re2.h"
31
32 constexpr char kBaselineDir[] = "fcp/tracing/test/testdata";
33
34 namespace fcp {
35 namespace {
36
37 // Replaces timestamp with ${TIME} and span ID with ${ID} in text trace output.
38 // Span IDs need to be replaced because of the lack of determinism in running
39 // multiple threads in parallel.
PostProcessOutput(std::string * input)40 inline bool PostProcessOutput(std::string* input) {
41 RE2 timestamp_and_id_pattern("\\d{4}-\\d{2}-\\d{2}T[[:^blank:]]*\\s\\d+");
42 return RE2::GlobalReplace(input, timestamp_and_id_pattern, "${TIME} ${ID}") >
43 0;
44 }
45
GetOutFileName(int id)46 std::string GetOutFileName(int id) {
47 return ConcatPath(testing::TempDir(), absl::StrCat(TestName(), id, ".out"));
48 }
49
GetBaselineFileName(int id)50 std::string GetBaselineFileName(int id) {
51 return ConcatPath(kBaselineDir, absl::StrCat(TestName(), id, ".baseline"));
52 }
53
VerifyAgainstBaseline(int id)54 absl::StatusOr<std::string> VerifyAgainstBaseline(int id) {
55 // Reading out file
56 std::string report = ReadFileToString(GetOutFileName(id)).value();
57 EXPECT_TRUE(PostProcessOutput(&report));
58 // Producing report which is expected to precisely match .baseline file.
59 std::ostringstream expected;
60 expected << "" << std::endl;
61
62 // Compare produced report with baseline.
63 std::string baseline_path = GetBaselineFileName(id);
64 return ::fcp::VerifyAgainstBaseline(baseline_path, report);
65 }
66
67 // Verifies that thread local tracing recorder can be changed on the same
68 // thread.
TEST(Tracing,ChangeThreadLocal)69 TEST(Tracing, ChangeThreadLocal) {
70 const int kCount = 2;
71 for (int i = 0; i < kCount; i++) {
72 const int id = i + 1;
73 TextTracingRecorder local_recorder(GetOutFileName(id), absl::UTCTimeZone());
74 ScopedTracingRecorder scoped_recorder(&local_recorder);
75 TracingSpan<SpanWithId> inner(id);
76 }
77
78 for (int i = 0; i < kCount; i++) {
79 const int id = i + 1;
80 auto status_s = VerifyAgainstBaseline(id);
81 ASSERT_TRUE(status_s.ok()) << status_s.status();
82 auto& diff = status_s.value();
83 if (!diff.empty()) {
84 FAIL() << diff;
85 }
86 }
87 }
88
TEST(Tracing,PerThread)89 TEST(Tracing, PerThread) {
90 const int kThreadCount = 2;
91 auto scheduler = CreateThreadPoolScheduler(kThreadCount);
92
93 for (int i = 0; i < kThreadCount; i++) {
94 scheduler->Schedule([&, i]() {
95 const int id = i + 1;
96 TextTracingRecorder local_recorder(GetOutFileName(id),
97 absl::UTCTimeZone());
98 ScopedTracingRecorder scoped_recorder(&local_recorder);
99 TracingSpan<SpanWithId> inner(id);
100 for (int k = 0; k < 5; k++) {
101 absl::SleepFor(absl::Milliseconds(10));
102 Trace<EventFoo>(id * 11, id * 111);
103 }
104 });
105 }
106
107 scheduler->WaitUntilIdle();
108
109 for (int i = 0; i < kThreadCount; i++) {
110 const int id = i + 1;
111 auto status_s = VerifyAgainstBaseline(id);
112 ASSERT_TRUE(status_s.ok()) << status_s.status();
113 auto& diff = status_s.value();
114 if (!diff.empty()) {
115 FAIL() << diff;
116 }
117 }
118 }
119
TEST(Tracing,UninstallRequired)120 TEST(Tracing, UninstallRequired) {
121 auto local_recorder =
122 std::make_shared<TextTracingRecorder>(absl::UTCTimeZone());
123 local_recorder->InstallAsThreadLocal();
124 ASSERT_DEATH(
125 local_recorder.reset(),
126 "Trace recorder must not be set as thread local at destruction time");
127 // Note that ASSERT_DEATH statement above runs in a separate process so it is
128 // still OK to uninstall the trace recorder here; otherwise this process
129 // would crash too on destruction of the trace recorder.
130 local_recorder->UninstallAsThreadLocal();
131 }
132
133 // Tests that setting the same tracing recorder is OK and that the number of
134 // InstallAsThreadLocal and UninstallAsThreadLocal must be matching.
TEST(Tracing,ReentrancySuccess)135 TEST(Tracing, ReentrancySuccess) {
136 auto local_recorder =
137 std::make_shared<TextTracingRecorder>(absl::UTCTimeZone());
138 local_recorder->InstallAsThreadLocal();
139 local_recorder->InstallAsThreadLocal();
140 local_recorder->UninstallAsThreadLocal();
141 local_recorder->UninstallAsThreadLocal();
142 }
143
144 // Verifies that not matching the number of InstallAsThreadLocal with
145 // UninstallAsThreadLocal results in a failure.
TEST(Tracing,ReentrancyFailure)146 TEST(Tracing, ReentrancyFailure) {
147 auto local_recorder =
148 std::make_shared<TextTracingRecorder>(absl::UTCTimeZone());
149 local_recorder->InstallAsThreadLocal();
150 // This simulates re-entracy by setting the same tracing recorder as
151 // thread local again.
152 local_recorder->InstallAsThreadLocal();
153 local_recorder->UninstallAsThreadLocal();
154 // At this point UninstallAsThreadLocal has been called only once, which isn't
155 // sufficient.
156 ASSERT_DEATH(
157 local_recorder.reset(),
158 "Trace recorder must not be set as thread local at destruction time");
159 // Note that ASSERT_DEATH statement above runs in a separate process so it is
160 // still necessary to uninstall the trace recorder here to make sure that
161 // the test doesn't crash in the main test process.
162 local_recorder->UninstallAsThreadLocal();
163 }
164
165 // Test that changing per-thread tracing recorder isn't allowed without
166 // uninstalling first.
TEST(Tracing,ChangingThreadLocalRecorderFails)167 TEST(Tracing, ChangingThreadLocalRecorderFails) {
168 TextTracingRecorder local_recorder1(absl::UTCTimeZone());
169 TextTracingRecorder local_recorder2(absl::UTCTimeZone());
170 local_recorder1.InstallAsThreadLocal();
171 ASSERT_DEATH(local_recorder2.InstallAsThreadLocal(),
172 "Only one tracing recorder instance per thread is supported");
173 // Note that ASSERT_DEATH statement above runs in a separate process so
174 // uninstalling local_recorder1 is still needed in the main test process.
175 local_recorder1.UninstallAsThreadLocal();
176 }
177
TEST(Tracing,UninstallingWrongThreadLocalRecorderFails)178 TEST(Tracing, UninstallingWrongThreadLocalRecorderFails) {
179 TextTracingRecorder local_recorder1(absl::UTCTimeZone());
180 TextTracingRecorder local_recorder2(absl::UTCTimeZone());
181 local_recorder1.InstallAsThreadLocal();
182 ASSERT_DEATH(local_recorder2.UninstallAsThreadLocal(),
183 "Attempting to uninstall thread local tracing recorder that "
184 "isn't currently installed");
185 // Note that ASSERT_DEATH statement above runs in a separate process so
186 // uninstalling local_recorder1 is still needed in the main test process.
187 local_recorder1.UninstallAsThreadLocal();
188 }
189
190 } // namespace
191 } // namespace fcp
192