Skip to content

Commit 8b27d1d

Browse files
ctillercopybara-github
authored andcommitted
[channelz] Log macro for channelz (grpc#39746)
Closes grpc#39746 COPYBARA_INTEGRATE_REVIEW=grpc#39746 from ctiller:plog3 5993915 PiperOrigin-RevId: 771745361
1 parent d87a9e6 commit 8b27d1d

File tree

18 files changed

+298
-151
lines changed

18 files changed

+298
-151
lines changed

BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,7 @@ grpc_cc_library(
12631263
"exec_ctx",
12641264
"gpr",
12651265
"grpc_public_hdrs",
1266+
"grpc_trace",
12661267
"parse_address",
12671268
"ref_counted_ptr",
12681269
"sockaddr_utils",

src/core/channelz/channel_trace.h

Lines changed: 192 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,17 @@
2727
#include <sys/types.h>
2828

2929
#include <algorithm>
30-
#include <limits>
3130
#include <memory>
3231
#include <tuple>
3332
#include <type_traits>
34-
#include <variant>
3533

3634
#include "absl/base/thread_annotations.h"
3735
#include "absl/strings/str_cat.h"
3836
#include "absl/strings/string_view.h"
39-
#include "src/core/util/dual_ref_counted.h"
37+
#include "src/core/lib/debug/trace_flags.h"
4038
#include "src/core/util/json/json.h"
4139
#include "src/core/util/memory_usage.h"
4240
#include "src/core/util/ref_counted_ptr.h"
43-
#include "src/core/util/single_set_ptr.h"
4441
#include "src/core/util/sync.h"
4542
#include "src/core/util/time.h"
4643

@@ -51,6 +48,100 @@ namespace testing {
5148
size_t GetSizeofTraceEvent(void);
5249
}
5350

51+
namespace detail {
52+
53+
class Renderer {
54+
public:
55+
virtual ~Renderer() = default;
56+
virtual std::string Render() const = 0;
57+
virtual size_t MemoryUsage() const = 0;
58+
};
59+
60+
struct StrCatFn {
61+
template <typename... Arg>
62+
std::string operator()(const Arg&... args) {
63+
return absl::StrCat(args...);
64+
}
65+
};
66+
67+
template <typename A>
68+
auto AdaptForStorage(A&& a) {
69+
using RawA = std::remove_reference_t<A>;
70+
if constexpr (std::is_same_v<std::decay_t<RawA>, const char*>) {
71+
return absl::string_view(a);
72+
} else {
73+
return RawA(std::forward<A>(a));
74+
}
75+
}
76+
77+
template <typename... Args>
78+
std::unique_ptr<Renderer> RendererFromConcatenationInner(Args&&... args) {
79+
class R final : public Renderer {
80+
public:
81+
explicit R(Args&&... args) : args_(std::forward<Args>(args)...) {}
82+
83+
std::string Render() const override {
84+
return std::apply(StrCatFn(), args_);
85+
}
86+
size_t MemoryUsage() const override {
87+
return MemoryUsageOf(args_) + sizeof(Renderer);
88+
}
89+
90+
private:
91+
std::tuple<Args...> args_;
92+
};
93+
return std::make_unique<R>(std::forward<Args>(args)...);
94+
}
95+
96+
template <typename... Args>
97+
std::unique_ptr<Renderer> RendererFromConcatenation(Args&&... args) {
98+
return RendererFromConcatenationInner(
99+
AdaptForStorage<Args>(std::forward<Args>(args))...);
100+
}
101+
102+
struct RendererFromConcatenationFn {
103+
template <typename... Args>
104+
auto operator()(Args&&... args) {
105+
return RendererFromConcatenation(std::forward<Args>(args)...);
106+
}
107+
};
108+
109+
template <typename N>
110+
void OutputLogFromLogExpr(N* out, std::unique_ptr<Renderer> renderer) {
111+
out->NewNode(std::move(renderer)).Commit();
112+
}
113+
114+
template <typename N, typename... T>
115+
class LogExpr {
116+
public:
117+
explicit LogExpr(N* node, T&&... values)
118+
: out_(node), values_(std::forward<T>(values)...) {}
119+
120+
~LogExpr() {
121+
if (out_ != nullptr) {
122+
OutputLogFromLogExpr(out_,
123+
std::apply(detail::RendererFromConcatenationFn(),
124+
std::move(values_)));
125+
}
126+
}
127+
128+
template <typename U>
129+
friend auto operator<<(LogExpr<N, T...>&& x, U&& u) {
130+
auto mk = [out = std::exchange(x.out_, nullptr),
131+
u = AdaptForStorage(std::forward<U>(u))](
132+
T&&... existing_values) mutable {
133+
return LogExpr<N, T..., decltype(u)>(
134+
out, std::forward<T>(existing_values)..., std::move(u));
135+
};
136+
return std::apply(mk, std::move(x.values_));
137+
}
138+
139+
private:
140+
N* out_;
141+
std::tuple<T...> values_;
142+
};
143+
} // namespace detail
144+
54145
class BaseNode;
55146

56147
// Object used to hold live data for a channel. This data is exposed via the
@@ -67,12 +158,7 @@ class ChannelTrace {
67158
explicit ChannelTrace(size_t max_memory)
68159
: max_memory_(std::min(max_memory, sizeof(Entry) * 32768)) {}
69160

70-
class Renderer {
71-
public:
72-
virtual ~Renderer() = default;
73-
virtual std::string Render() const = 0;
74-
virtual size_t MemoryUsage() const = 0;
75-
};
161+
using Renderer = detail::Renderer;
76162

77163
enum Severity {
78164
Unset = 0, // never to be used
@@ -147,7 +233,7 @@ class ChannelTrace {
147233
// Returns a new `Node` object representing the child.
148234
// If this node is invalid (e.g., default-constructed or moved-from),
149235
// an invalid `Node` is returned.
150-
[[nodiscard]] Node NewChild(std::unique_ptr<Renderer> renderer) {
236+
[[nodiscard]] Node NewNode(std::unique_ptr<Renderer> renderer) {
151237
if (trace_ == nullptr || ref_.id == kSentinelId) return Node();
152238
return Node(trace_, trace_->AppendEntry(ref_, std::move(renderer)));
153239
}
@@ -160,9 +246,10 @@ class ChannelTrace {
160246
// If this node is invalid (e.g., default-constructed or moved-from),
161247
// an invalid `Node` is returned.
162248
template <typename... Args>
163-
[[nodiscard]] Node NewChild(Args&&... args) {
249+
[[nodiscard]] Node NewNode(Args&&... args) {
164250
if (trace_ == nullptr || ref_.id == kSentinelId) return Node();
165-
return NewChild(RendererFromConcatenation(std::forward<Args>(args)...));
251+
return NewNode(
252+
detail::RendererFromConcatenation(std::forward<Args>(args)...));
166253
}
167254

168255
// Marks the trace entry associated with this `Node` as permanent.
@@ -174,6 +261,8 @@ class ChannelTrace {
174261
committed_ = true;
175262
}
176263

264+
bool ProducesOutput() const { return ref_.id != kSentinelId; }
265+
177266
private:
178267
friend class ChannelTrace;
179268

@@ -203,7 +292,8 @@ class ChannelTrace {
203292

204293
template <typename... Args>
205294
[[nodiscard]] Node NewNode(Args&&... args) {
206-
return NewNode(RendererFromConcatenation(std::forward<Args>(args)...));
295+
return NewNode(
296+
detail::RendererFromConcatenation(std::forward<Args>(args)...));
207297
}
208298

209299
// Creates and returns the raw Json object, so a parent channelz
@@ -215,6 +305,8 @@ class ChannelTrace {
215305
RefCountedPtr<BaseNode>)>
216306
callback) const ABSL_LOCKS_EXCLUDED(mu_);
217307

308+
bool ProducesOutput() const { return max_memory_ > 0; }
309+
218310
std::string creation_timestamp() const;
219311
uint64_t num_events_logged() const {
220312
MutexLock lock(&mu_);
@@ -281,49 +373,6 @@ class ChannelTrace {
281373
void DropEntry(EntryRef entry) ABSL_LOCKS_EXCLUDED(mu_);
282374
void DropEntryId(uint16_t id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
283375

284-
struct StrCatFn {
285-
template <typename... Arg>
286-
std::string operator()(const Arg&... args) {
287-
return absl::StrCat(args...);
288-
}
289-
};
290-
291-
template <typename A>
292-
static auto AdaptForStorage(A&& a) {
293-
using RawA = std::remove_reference_t<A>;
294-
if constexpr (std::is_same_v<std::decay_t<RawA>, const char*>) {
295-
return absl::string_view(a);
296-
} else {
297-
return RawA(a);
298-
}
299-
}
300-
301-
template <typename... Args>
302-
static std::unique_ptr<Renderer> RendererFromConcatenationInner(
303-
Args&&... args) {
304-
class R final : public Renderer {
305-
public:
306-
explicit R(Args&&... args) : args_(std::forward<Args>(args)...) {}
307-
308-
std::string Render() const override {
309-
return std::apply(StrCatFn(), args_);
310-
}
311-
size_t MemoryUsage() const override {
312-
return MemoryUsageOf(args_) + sizeof(Renderer);
313-
}
314-
315-
private:
316-
std::tuple<Args...> args_;
317-
};
318-
return std::make_unique<R>(std::forward<Args>(args)...);
319-
}
320-
321-
template <typename... Args>
322-
static std::unique_ptr<Renderer> RendererFromConcatenation(Args&&... args) {
323-
return RendererFromConcatenationInner(
324-
AdaptForStorage<Args>(std::forward<Args>(args))...);
325-
}
326-
327376
void RenderEntry(const Entry& entry,
328377
absl::FunctionRef<void(gpr_timespec, Severity, std::string,
329378
RefCountedPtr<BaseNode>)>
@@ -341,7 +390,93 @@ class ChannelTrace {
341390
std::vector<Entry> entries_ ABSL_GUARDED_BY(mu_);
342391
};
343392

393+
// A node that GRPC_CHANNELZ_TRACE can output to, that also
394+
// logs to absl LOG(INFO) if a particular TraceFlag is enabled.
395+
// Provides a way to elevate GRPC_TRACE_LOG statements to channelz
396+
// also.
397+
class TraceNode {
398+
public:
399+
TraceNode() = default;
400+
401+
template <typename F>
402+
TraceNode(ChannelTrace::Node node, TraceFlag& flag, F prefix)
403+
: node_(std::move(node)) {
404+
if (GPR_UNLIKELY(flag.enabled())) {
405+
log_to_absl_ = std::make_unique<std::string>(prefix());
406+
}
407+
}
408+
409+
bool ProducesOutput() const {
410+
return node_.ProducesOutput() || log_to_absl_ != nullptr;
411+
}
412+
413+
void Finish(std::unique_ptr<detail::Renderer> renderer) {
414+
if (GPR_UNLIKELY(log_to_absl_ != nullptr)) {
415+
LOG(INFO) << *log_to_absl_ << renderer->Render();
416+
}
417+
node_.NewNode(std::move(renderer)).Commit();
418+
}
419+
420+
void Commit() { node_.Commit(); }
421+
422+
private:
423+
ChannelTrace::Node node_;
424+
std::unique_ptr<std::string> log_to_absl_;
425+
};
426+
427+
namespace detail {
428+
inline ChannelTrace* LogOutputFrom(ChannelTrace& t) {
429+
if (!t.ProducesOutput()) return nullptr;
430+
return &t;
431+
}
432+
433+
inline ChannelTrace::Node* LogOutputFrom(ChannelTrace::Node& n) {
434+
if (!n.ProducesOutput()) return nullptr;
435+
return &n;
436+
}
437+
438+
inline TraceNode* LogOutputFrom(TraceNode& n) {
439+
if (!n.ProducesOutput()) return nullptr;
440+
return &n;
441+
}
442+
443+
inline void OutputLogFromLogExpr(TraceNode* out,
444+
std::unique_ptr<Renderer> renderer) {
445+
out->Finish(std::move(renderer));
446+
}
447+
} // namespace detail
448+
344449
} // namespace channelz
345450
} // namespace grpc_core
346451

452+
// Log like LOG() to a channelz object (and potentially as part of a GRPC_TRACE
453+
// log with channelz::TraceNode).
454+
//
455+
// `output` is one of a ChannelTrace, ChannelTrace::Node or a TraceNode.
456+
//
457+
// This trace always commits - and the channelz node is inaccessible as a
458+
// result. Use it for annotation like things, and if commit-ability is
459+
// important, put it under a parent node and use that for `output`.
460+
//
461+
// Notes on this weird macro!
462+
// - We want this to be a statement level thing, such that end of statement ==>
463+
// we can commit the log line.
464+
// - To do that we need to ensure we're not an expression, so we want an if,
465+
// for, while, or do statement enclosing things.
466+
// - But hey! we want to stream after the GRPC_CHANNELZ_LOG(foo), so we want an
467+
// if right - if (we_can_output) output << s1 << s2 << s3;
468+
// - But hey! now if someone copy/pastes this in front of an else then we
469+
// bind with that else, and boom we've got a security hole... so let's not do
470+
// that.
471+
// - So, the for here acts as an if (the output = nullptr part ensures we don't)
472+
// actually loop), ensures we have a statement, and ensures we don't
473+
// accidentally bind with a trailing else.
474+
// - And of course we skip wrapping things in {} because we really like that
475+
// GRPC_CHANNELZ_LOG(foo) << "hello!"; syntax.
476+
#define GRPC_CHANNELZ_LOG(output) \
477+
for (auto* out = grpc_core::channelz::detail::LogOutputFrom(output); \
478+
out != nullptr; out = nullptr) \
479+
grpc_core::channelz::detail::LogExpr< \
480+
std::remove_reference_t<decltype(*out)>>(out)
481+
347482
#endif // GRPC_SRC_CORE_CHANNELZ_CHANNEL_TRACE_H

src/core/channelz/channelz.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <optional>
3333
#include <set>
3434
#include <string>
35+
#include <type_traits>
3536
#include <utility>
3637

3738
#include "absl/base/thread_annotations.h"
@@ -178,6 +179,7 @@ class BaseNode : public DualRefCounted<BaseNode> {
178179
ChannelTrace::Node NewTraceNode(Args&&... args) {
179180
return trace_.NewNode(std::forward<Args>(args)...);
180181
}
182+
ChannelTrace& mutable_trace() { return trace_; }
181183

182184
protected:
183185
void PopulateJsonFromDataSources(Json::Object& json);
@@ -207,6 +209,19 @@ class BaseNode : public DualRefCounted<BaseNode> {
207209
ChannelTrace trace_;
208210
};
209211

212+
namespace detail {
213+
inline ChannelTrace* LogOutputFrom(BaseNode* n) {
214+
if (n == nullptr) return nullptr;
215+
return LogOutputFrom(n->mutable_trace());
216+
}
217+
218+
template <typename N>
219+
inline std::enable_if_t<std::is_base_of_v<BaseNode, N>, ChannelTrace*>
220+
LogOutputFrom(const RefCountedPtr<N>& n) {
221+
return LogOutputFrom(n.get());
222+
}
223+
} // namespace detail
224+
210225
class ZTrace {
211226
public:
212227
virtual ~ZTrace() = default;

0 commit comments

Comments
 (0)