aboutsummaryrefslogtreecommitdiff
path: root/fcp/tracing/test/thread_local_tracing_recorder_test.cc
blob: 8bceaa41f029464f751d3717a4cf032c3a0cfb6c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <fstream>
#include <memory>
#include <string>

#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "fcp/base/monitoring.h"
#include "fcp/base/platform.h"
#include "fcp/base/scheduler.h"
#include "fcp/testing/testing.h"
#include "fcp/tracing/scoped_tracing_recorder.h"
#include "fcp/tracing/test/tracing_schema.h"
#include "fcp/tracing/text_tracing_recorder.h"
#include "re2/re2.h"

constexpr char kBaselineDir[] = "fcp/tracing/test/testdata";

namespace fcp {
namespace {

// Replaces timestamp with ${TIME} and span ID with ${ID} in text trace output.
// Span IDs need to be replaced because of the lack of determinism in running
// multiple threads in parallel.
inline bool PostProcessOutput(std::string* input) {
  RE2 timestamp_and_id_pattern("\\d{4}-\\d{2}-\\d{2}T[[:^blank:]]*\\s\\d+");
  return RE2::GlobalReplace(input, timestamp_and_id_pattern, "${TIME} ${ID}") >
         0;
}

std::string GetOutFileName(int id) {
  return ConcatPath(testing::TempDir(), absl::StrCat(TestName(), id, ".out"));
}

std::string GetBaselineFileName(int id) {
  return ConcatPath(kBaselineDir, absl::StrCat(TestName(), id, ".baseline"));
}

absl::StatusOr<std::string> VerifyAgainstBaseline(int id) {
  // Reading out file
  std::string report = ReadFileToString(GetOutFileName(id)).value();
  EXPECT_TRUE(PostProcessOutput(&report));
  // Producing report which is expected to precisely match .baseline file.
  std::ostringstream expected;
  expected << "" << std::endl;

  // Compare produced report with baseline.
  std::string baseline_path = GetBaselineFileName(id);
  return ::fcp::VerifyAgainstBaseline(baseline_path, report);
}

// Verifies that thread local tracing recorder can be changed on the same
// thread.
TEST(Tracing, ChangeThreadLocal) {
  const int kCount = 2;
  for (int i = 0; i < kCount; i++) {
    const int id = i + 1;
    TextTracingRecorder local_recorder(GetOutFileName(id), absl::UTCTimeZone());
    ScopedTracingRecorder scoped_recorder(&local_recorder);
    TracingSpan<SpanWithId> inner(id);
  }

  for (int i = 0; i < kCount; i++) {
    const int id = i + 1;
    auto status_s = VerifyAgainstBaseline(id);
    ASSERT_TRUE(status_s.ok()) << status_s.status();
    auto& diff = status_s.value();
    if (!diff.empty()) {
      FAIL() << diff;
    }
  }
}

TEST(Tracing, PerThread) {
  const int kThreadCount = 2;
  auto scheduler = CreateThreadPoolScheduler(kThreadCount);

  for (int i = 0; i < kThreadCount; i++) {
    scheduler->Schedule([&, i]() {
      const int id = i + 1;
      TextTracingRecorder local_recorder(GetOutFileName(id),
                                         absl::UTCTimeZone());
      ScopedTracingRecorder scoped_recorder(&local_recorder);
      TracingSpan<SpanWithId> inner(id);
      for (int k = 0; k < 5; k++) {
        absl::SleepFor(absl::Milliseconds(10));
        Trace<EventFoo>(id * 11, id * 111);
      }
    });
  }

  scheduler->WaitUntilIdle();

  for (int i = 0; i < kThreadCount; i++) {
    const int id = i + 1;
    auto status_s = VerifyAgainstBaseline(id);
    ASSERT_TRUE(status_s.ok()) << status_s.status();
    auto& diff = status_s.value();
    if (!diff.empty()) {
      FAIL() << diff;
    }
  }
}

TEST(Tracing, UninstallRequired) {
  auto local_recorder =
      std::make_shared<TextTracingRecorder>(absl::UTCTimeZone());
  local_recorder->InstallAsThreadLocal();
  ASSERT_DEATH(
      local_recorder.reset(),
      "Trace recorder must not be set as thread local at destruction time");
  // Note that ASSERT_DEATH statement above runs in a separate process so it is
  // still OK to uninstall the trace recorder here; otherwise this process
  // would crash too on destruction of the trace recorder.
  local_recorder->UninstallAsThreadLocal();
}

// Tests that setting the same tracing recorder is OK and that the number of
// InstallAsThreadLocal and UninstallAsThreadLocal must be matching.
TEST(Tracing, ReentrancySuccess) {
  auto local_recorder =
      std::make_shared<TextTracingRecorder>(absl::UTCTimeZone());
  local_recorder->InstallAsThreadLocal();
  local_recorder->InstallAsThreadLocal();
  local_recorder->UninstallAsThreadLocal();
  local_recorder->UninstallAsThreadLocal();
}

// Verifies that not matching the number of InstallAsThreadLocal with
// UninstallAsThreadLocal results in a failure.
TEST(Tracing, ReentrancyFailure) {
  auto local_recorder =
      std::make_shared<TextTracingRecorder>(absl::UTCTimeZone());
  local_recorder->InstallAsThreadLocal();
  // This simulates re-entracy by setting the same tracing recorder as
  // thread local again.
  local_recorder->InstallAsThreadLocal();
  local_recorder->UninstallAsThreadLocal();
  // At this point UninstallAsThreadLocal has been called only once, which isn't
  // sufficient.
  ASSERT_DEATH(
      local_recorder.reset(),
      "Trace recorder must not be set as thread local at destruction time");
  // Note that ASSERT_DEATH statement above runs in a separate process so it is
  // still necessary to uninstall the trace recorder here to make sure that
  // the test doesn't crash in the main test process.
  local_recorder->UninstallAsThreadLocal();
}

// Test that changing per-thread tracing recorder isn't allowed without
// uninstalling first.
TEST(Tracing, ChangingThreadLocalRecorderFails) {
  TextTracingRecorder local_recorder1(absl::UTCTimeZone());
  TextTracingRecorder local_recorder2(absl::UTCTimeZone());
  local_recorder1.InstallAsThreadLocal();
  ASSERT_DEATH(local_recorder2.InstallAsThreadLocal(),
               "Only one tracing recorder instance per thread is supported");
  // Note that ASSERT_DEATH statement above runs in a separate process so
  // uninstalling local_recorder1 is still needed in the main test process.
  local_recorder1.UninstallAsThreadLocal();
}

TEST(Tracing, UninstallingWrongThreadLocalRecorderFails) {
  TextTracingRecorder local_recorder1(absl::UTCTimeZone());
  TextTracingRecorder local_recorder2(absl::UTCTimeZone());
  local_recorder1.InstallAsThreadLocal();
  ASSERT_DEATH(local_recorder2.UninstallAsThreadLocal(),
               "Attempting to uninstall thread local tracing recorder that "
               "isn't currently installed");
  // Note that ASSERT_DEATH statement above runs in a separate process so
  // uninstalling local_recorder1 is still needed in the main test process.
  local_recorder1.UninstallAsThreadLocal();
}

}  // namespace
}  // namespace fcp