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

Commit c4a4f8a

Browse files
author
Jack Ferris
committed
Add support for StrictSpans mode in Tracing
Goal: StrictSpans mode is meant to enforce the invariant that a parent span cannot end while it has children running. If a parent span ends with active children, it is held open and its end is triggered when the last child ends. This works even with a chain of spans, the last child can trigger all parents up to the root to end. Details: 1. Switch running_span_store_impl to key its map on SpanId instead of the pointer 2. Add FindSpanById method on RunningSpanStoreImpl 3. Add reference count called active_child_count_ on SpanImpl to keep track of active children 4. Add logic at span start and end time to update the reference count on parents, 5. Add tests
1 parent 135850f commit c4a4f8a

12 files changed

+230
-30
lines changed

opencensus/trace/internal/running_span_store_impl.cc

+17-12
Original file line numberDiff line numberDiff line change
@@ -34,35 +34,40 @@ namespace opencensus {
3434
namespace trace {
3535
namespace exporter {
3636

37-
namespace {
38-
// Returns the memory address of the SpanImpl object, to be used as a key into
39-
// the map of spans.
40-
uintptr_t GetKey(const SpanImpl* span) {
41-
return reinterpret_cast<uintptr_t>(span);
42-
}
43-
} // namespace
44-
4537
RunningSpanStoreImpl* RunningSpanStoreImpl::Get() {
4638
static RunningSpanStoreImpl* global_running_span_store =
4739
new RunningSpanStoreImpl;
4840
return global_running_span_store;
4941
}
5042

51-
void RunningSpanStoreImpl::AddSpan(const std::shared_ptr<SpanImpl>& span) {
43+
void RunningSpanStoreImpl::AddSpan(const SpanId& id,
44+
const std::shared_ptr<SpanImpl>& span) {
5245
absl::MutexLock l(&mu_);
53-
spans_.insert({GetKey(span.get()), span});
46+
spans_.insert({id, span});
5447
}
5548

56-
bool RunningSpanStoreImpl::RemoveSpan(const std::shared_ptr<SpanImpl>& span) {
49+
bool RunningSpanStoreImpl::RemoveSpan(const SpanId& id) {
5750
absl::MutexLock l(&mu_);
58-
auto iter = spans_.find(GetKey(span.get()));
51+
auto iter = spans_.find(id);
5952
if (iter == spans_.end()) {
6053
return false; // Not tracked.
6154
}
6255
spans_.erase(iter);
6356
return true;
6457
}
6558

59+
bool RunningSpanStoreImpl::FindSpan(const SpanId& id,
60+
std::shared_ptr<SpanImpl>* span) {
61+
absl::MutexLock l(&mu_);
62+
auto iter = spans_.find(id);
63+
if (iter != spans_.end()) {
64+
*span = iter->second;
65+
return true;
66+
} else {
67+
return false;
68+
}
69+
}
70+
6671
RunningSpanStore::Summary RunningSpanStoreImpl::GetSummary() const {
6772
RunningSpanStore::Summary summary;
6873
absl::MutexLock l(&mu_);

opencensus/trace/internal/running_span_store_impl.h

+14-4
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@ class RunningSpanStoreImpl {
3838
static RunningSpanStoreImpl* Get();
3939

4040
// Adds a new running Span.
41-
void AddSpan(const std::shared_ptr<SpanImpl>& span) LOCKS_EXCLUDED(mu_);
41+
void AddSpan(const SpanId& id, const std::shared_ptr<SpanImpl>& span)
42+
LOCKS_EXCLUDED(mu_);
43+
44+
// Finds a Span with id SpanId, returns false if not found.
45+
bool FindSpan(const SpanId& id, std::shared_ptr<SpanImpl>* span)
46+
LOCKS_EXCLUDED(mu_);
4247

4348
// Removes a Span that's no longer running. Returns true on success, false if
4449
// that Span was not being tracked.
45-
bool RemoveSpan(const std::shared_ptr<SpanImpl>& span) LOCKS_EXCLUDED(mu_);
50+
bool RemoveSpan(const SpanId& id) LOCKS_EXCLUDED(mu_);
4651

4752
// Returns a summary of the data available in the RunningSpanStore.
4853
RunningSpanStore::Summary GetSummary() const LOCKS_EXCLUDED(mu_);
@@ -61,8 +66,13 @@ class RunningSpanStoreImpl {
6166

6267
mutable absl::Mutex mu_;
6368

64-
// The key is the memory address of the underlying SpanImpl object.
65-
std::unordered_map<uintptr_t, std::shared_ptr<SpanImpl>> spans_
69+
70+
// Necessary for using SpanId as the key in the map.
71+
struct SpanIdHash {
72+
std::size_t operator()(SpanId const& s) const noexcept { return s.hash(); }
73+
};
74+
75+
std::unordered_map<SpanId, std::shared_ptr<SpanImpl>, SpanIdHash> spans_
6676
GUARDED_BY(mu_);
6777
};
6878

opencensus/trace/internal/span.cc

+48-9
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,22 @@ class SpanGenerator {
6969
SpanId span_id = GenerateRandomSpanId();
7070
TraceId trace_id;
7171
SpanId parent_span_id;
72-
TraceOptions trace_options;
72+
TraceOptions trace_options = options.trace_options;
7373
if (parent_ctx == nullptr) {
7474
trace_id = GenerateRandomTraceId();
7575
} else {
7676
trace_id = parent_ctx->trace_id();
7777
parent_span_id = parent_ctx->span_id();
7878
trace_options = parent_ctx->trace_options();
79+
if (trace_options.HasStrictSpans() && trace_options.IsSampled() && !has_remote_parent ) {
80+
std::shared_ptr<SpanImpl> span;
81+
if (exporter::RunningSpanStoreImpl::Get()->FindSpan(parent_span_id,
82+
&span)) {
83+
span->IncrementChildCount();
84+
} else {
85+
assert(false);
86+
}
87+
}
7988
}
8089
if (!trace_options.IsSampled()) {
8190
bool should_sample = false;
@@ -135,10 +144,15 @@ Span Span::StartSpanWithRemoteParent(absl::string_view name,
135144
Span::Span(const SpanContext& context, SpanImpl* impl)
136145
: context_(context), span_impl_(impl) {
137146
if (IsRecording()) {
138-
exporter::RunningSpanStoreImpl::Get()->AddSpan(span_impl_);
147+
exporter::RunningSpanStoreImpl::Get()->AddSpan(context.span_id(),
148+
span_impl_);
139149
}
140150
}
141151

152+
void Span::IncrementChildCount() const {
153+
span_impl_->IncrementChildCount();
154+
}
155+
142156
void Span::AddAttribute(absl::string_view key,
143157
AttributeValueRef attribute) const {
144158
if (IsRecording()) {
@@ -204,13 +218,38 @@ void Span::SetStatus(StatusCode canonical_code,
204218

205219
void Span::End() const {
206220
if (IsRecording()) {
207-
if (!span_impl_->End()) {
208-
// The Span already ended, ignore this call.
209-
return;
210-
}
211-
exporter::RunningSpanStoreImpl::Get()->RemoveSpan(span_impl_);
212-
exporter::LocalSpanStoreImpl::Get()->AddSpan(span_impl_);
213-
exporter::SpanExporterImpl::Get()->AddSpan(span_impl_);
221+
EndSpanById(context_.span_id());
222+
}
223+
}
224+
225+
void Span::EndSpanById(const SpanId& id) {
226+
std::shared_ptr<SpanImpl> span;
227+
auto found = exporter::RunningSpanStoreImpl::Get()->FindSpan(id, &span);
228+
assert(found && "Cannot call end span on an invalid ID");
229+
if (!found) {
230+
return;
231+
}
232+
233+
if(!span->End()) {
234+
return;
235+
}
236+
exporter::RunningSpanStoreImpl::Get()->RemoveSpan(id);
237+
exporter::LocalSpanStoreImpl::Get()->AddSpan(span);
238+
exporter::SpanExporterImpl::Get()->AddSpan(span);
239+
240+
if (span->context().trace_options().HasStrictSpans() &&
241+
(!span->remote_parent() &&
242+
span->parent_span_id().IsValid())) {
243+
std::shared_ptr<SpanImpl> parent_span;
244+
245+
found = exporter::RunningSpanStoreImpl::Get()->FindSpan(
246+
span->parent_span_id(), &parent_span);
247+
assert(found && "Parent Span Id Not Found in Strict Spans Mode");
248+
249+
if (!found) {
250+
return;
251+
}
252+
parent_span->DecrementChildCount();
214253
}
215254
}
216255

opencensus/trace/internal/span_id.cc

+10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "opencensus/trace/span_id.h"
1616

1717
#include <cstring>
18+
#include <functional>
1819
#include <string>
1920

2021
#include "absl/strings/escaping.h"
@@ -42,6 +43,15 @@ bool SpanId::IsValid() const {
4243
return tmp != 0;
4344
}
4445

46+
std::size_t SpanId::hash() const {
47+
// SpanIds are currently 64 bits. this hash function is only ever used
48+
// internally within a given process, so this can be easily changed if we
49+
// ever increase the size.
50+
std::hash<uint64_t> hash_fn;
51+
uint64_t val = *(reinterpret_cast<const uint64_t *>(rep_));
52+
return hash_fn(val);
53+
}
54+
4555
void SpanId::CopyTo(uint8_t *buf) const { memcpy(buf, rep_, kSize); }
4656

4757
} // namespace trace

opencensus/trace/internal/span_impl.cc

+26-3
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ std::unordered_map<std::string, exporter::AttributeValue> CopyAttributes(
7373
}
7474
} // namespace
7575

76-
// SpanImpl::SpanImpl() : has_ended_(false), remote_parent_(false) {}
77-
7876
SpanImpl::SpanImpl(const SpanContext& context, const TraceParams& trace_params,
7977
absl::string_view name, const SpanId& parent_span_id,
8078
bool remote_parent)
@@ -87,7 +85,9 @@ SpanImpl::SpanImpl(const SpanContext& context, const TraceParams& trace_params,
8785
links_(trace_params.max_links),
8886
attributes_(trace_params.max_attributes),
8987
has_ended_(false),
90-
remote_parent_(remote_parent) {}
88+
remote_parent_(remote_parent),
89+
active_child_count_(0),
90+
end_requested_(false) {}
9191

9292
void SpanImpl::AddAttributes(AttributesRef attributes) {
9393
absl::MutexLock l(&mu_);
@@ -139,6 +139,10 @@ void SpanImpl::SetStatus(exporter::Status&& status) {
139139

140140
bool SpanImpl::End() {
141141
absl::MutexLock l(&mu_);
142+
if (context_.trace_options().HasStrictSpans() && active_child_count_ > 0) {
143+
end_requested_ = true;
144+
return false;
145+
}
142146
if (has_ended_) {
143147
assert(false && "Invalid attempt to End() the same Span more than once.");
144148
// In non-debug builds, just ignore the second End().
@@ -154,6 +158,25 @@ bool SpanImpl::HasEnded() const {
154158
return has_ended_;
155159
}
156160

161+
void SpanImpl::IncrementChildCount() const {
162+
absl::MutexLock l(&mu_);
163+
active_child_count_++;
164+
}
165+
166+
void SpanImpl::DecrementChildCount() {
167+
bool shouldEnd = false;
168+
{
169+
absl::MutexLock l(&mu_);
170+
assert(active_child_count_ > 0);
171+
active_child_count_--;
172+
shouldEnd = (active_child_count_ == 0 && end_requested_);
173+
}
174+
175+
if (shouldEnd) {
176+
Span::EndSpanById(context_.span_id());
177+
}
178+
}
179+
157180
exporter::SpanData SpanImpl::ToSpanData() const {
158181
absl::MutexLock l(&mu_);
159182
// Make a deep copy of attributes.

opencensus/trace/internal/span_impl.h

+14
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ class SpanImpl final {
102102

103103
SpanId parent_span_id() const { return parent_span_id_; }
104104

105+
bool remote_parent() const { return remote_parent_; }
106+
107+
// Increments the counter of open child spans below this span.
108+
void IncrementChildCount() const;
109+
110+
// Decrements the open child counter AND if the counter becomes
111+
// zero then End() is called on the span.
112+
void DecrementChildCount();
113+
105114
private:
106115
friend class ::opencensus::trace::exporter::RunningSpanStoreImpl;
107116
friend class ::opencensus::trace::exporter::LocalSpanStoreImpl;
@@ -138,6 +147,11 @@ class SpanImpl final {
138147
bool has_ended_ GUARDED_BY(mu_);
139148
// True if the parent Span is in a different process.
140149
const bool remote_parent_;
150+
// Counts the number of open children linked to this span.
151+
mutable uint32_t active_child_count_ GUARDED_BY(mu_);
152+
// Keeps track if End() was called on this span while it had open child
153+
// spans and is waiting on them.
154+
mutable bool end_requested_ GUARDED_BY(mu_);
141155
};
142156

143157
} // namespace trace

opencensus/trace/internal/span_test.cc

+60
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,66 @@ TEST(SpanTest, FullSpanTest) {
282282
expect_message_event(3, 4, 5, data.message_events().events()[1].event());
283283
}
284284

285+
TEST(SpanTest, StrictSpanTest) {
286+
AlwaysSampler sampler;
287+
TraceOptions t = TraceOptions().WithStrictSpans(true);
288+
StartSpanOptions opts = {&sampler, {}, t};
289+
auto span = ::opencensus::trace::Span::StartSpan("MyRootSpan", nullptr, opts);
290+
auto child = Span::StartSpan("child", &span);
291+
EXPECT_TRUE(span.context().IsValid());
292+
293+
// Calling end on the parent span with an open child should fail/not cause it to end
294+
span.End();
295+
const exporter::SpanData data = SpanTestPeer::ToSpanData(&span);
296+
EXPECT_EQ("MyRootSpan", data.name());
297+
EXPECT_EQ(false, data.has_ended());
298+
299+
// Now end the child and see that the parent span is ended as well
300+
child.End();
301+
const exporter::SpanData data2 = SpanTestPeer::ToSpanData(&span);
302+
EXPECT_EQ("MyRootSpan", data2.name());
303+
EXPECT_EQ(true, data2.has_ended());
304+
305+
306+
// Now, try ending child first and verify that that doesn't end the parent span
307+
auto span2 = ::opencensus::trace::Span::StartSpan("root2", nullptr, opts);
308+
auto child2 = Span::StartSpan("child2", &span2);
309+
EXPECT_TRUE(span2.context().IsValid());
310+
311+
// Now end the child and see that the parent span is ended as well
312+
child2.End();
313+
const exporter::SpanData root2_pre = SpanTestPeer::ToSpanData(&span2);
314+
EXPECT_EQ("root2", root2_pre.name());
315+
EXPECT_EQ(false, root2_pre.has_ended());
316+
317+
318+
// Calling end on the parent span with an open child should fail/not cause it to end
319+
span2.End();
320+
const exporter::SpanData root2_post = SpanTestPeer::ToSpanData(&span2);
321+
EXPECT_EQ("root2", root2_post.name());
322+
EXPECT_EQ(true, root2_post.has_ended());
323+
324+
325+
// Now try with 3 tiers
326+
auto grandparent = ::opencensus::trace::Span::StartSpan("grandpa", nullptr, opts);
327+
auto parent = ::opencensus::trace::Span::StartSpan("parent", &grandparent, opts);
328+
auto kid = Span::StartSpan("kid", &parent);
329+
330+
// End both grandparent and parent, neither should have ended
331+
grandparent.End();
332+
parent.End();
333+
const exporter::SpanData grandparent_open_data = SpanTestPeer::ToSpanData(&grandparent);
334+
EXPECT_EQ(false, grandparent_open_data.has_ended());
335+
const exporter::SpanData parent_open_data = SpanTestPeer::ToSpanData(&parent);
336+
EXPECT_EQ(false, parent_open_data.has_ended());
337+
338+
kid.End();
339+
const exporter::SpanData grandparent_closed_data = SpanTestPeer::ToSpanData(&grandparent);
340+
EXPECT_EQ(true, grandparent_closed_data.has_ended());
341+
const exporter::SpanData parent_closed_data = SpanTestPeer::ToSpanData(&parent);
342+
EXPECT_EQ(true, parent_closed_data.has_ended());
343+
}
344+
285345
TEST(SpanTest, ChildInheritsTraceOption) {
286346
constexpr uint8_t trace_id[] = {1, 2, 3, 4, 5, 6, 7, 8,
287347
9, 10, 11, 12, 13, 14, 15, 16};

opencensus/trace/internal/trace_options.cc

+10
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ namespace trace {
2525
namespace {
2626
// One bit per option.
2727
constexpr uint8_t kIsSampled = 1;
28+
constexpr uint8_t kStrictSpans = 2;
2829
} // namespace
2930

3031
TraceOptions::TraceOptions(const uint8_t* buf) { memcpy(rep_, buf, kSize); }
3132

3233
bool TraceOptions::IsSampled() const { return rep_[0] & kIsSampled; }
3334

35+
bool TraceOptions::HasStrictSpans() const { return rep_[0] & kStrictSpans; }
36+
3437
bool TraceOptions::operator==(const TraceOptions& that) const {
3538
return memcmp(rep_, that.rep_, kSize) == 0;
3639
}
@@ -49,5 +52,12 @@ TraceOptions TraceOptions::WithSampling(bool is_sampled) const {
4952
return TraceOptions(buf);
5053
}
5154

55+
TraceOptions TraceOptions::WithStrictSpans(bool strict_spans) const {
56+
uint8_t buf[kSize];
57+
CopyTo(buf);
58+
buf[0] = (buf[0] & ~kStrictSpans) | (strict_spans ? kStrictSpans : 0);
59+
return TraceOptions(buf);
60+
}
61+
5262
} // namespace trace
5363
} // namespace opencensus

opencensus/trace/internal/trace_options_test.cc

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ TEST(TraceOptionsTest, SetSampled) {
3232
opts = opts.WithSampling(false);
3333
EXPECT_EQ("00", opts.ToHex());
3434
EXPECT_FALSE(opts.IsSampled());
35+
EXPECT_FALSE(opts.HasStrictSpans());
36+
opts = opts.WithStrictSpans(true);
37+
EXPECT_EQ("02", opts.ToHex());
38+
EXPECT_TRUE(opts.HasStrictSpans());
3539
}
3640

3741
TEST(TraceOptionsTest, Comparison) {

0 commit comments

Comments
 (0)