| File: | build/source/clang-tools-extra/clangd/support/Trace.cpp |
| Warning: | line 107, column 11 Value stored to 'OriginTime' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | //===--- Trace.cpp - Performance tracing facilities -----------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "support/Trace.h" |
| 10 | #include "support/Context.h" |
| 11 | #include "llvm/ADT/DenseSet.h" |
| 12 | #include "llvm/ADT/ScopeExit.h" |
| 13 | #include "llvm/ADT/StringRef.h" |
| 14 | #include "llvm/Support/Chrono.h" |
| 15 | #include "llvm/Support/FormatVariadic.h" |
| 16 | #include "llvm/Support/Threading.h" |
| 17 | #include <atomic> |
| 18 | #include <chrono> |
| 19 | #include <memory> |
| 20 | #include <mutex> |
| 21 | #include <optional> |
| 22 | |
| 23 | namespace clang { |
| 24 | namespace clangd { |
| 25 | namespace trace { |
| 26 | |
| 27 | namespace { |
| 28 | // The current implementation is naive: each thread writes to Out guarded by Mu. |
| 29 | // Perhaps we should replace this by something that disturbs performance less. |
| 30 | class JSONTracer : public EventTracer { |
| 31 | public: |
| 32 | JSONTracer(llvm::raw_ostream &OS, bool Pretty) |
| 33 | : Out(OS, Pretty ? 2 : 0), Start(std::chrono::system_clock::now()) { |
| 34 | // The displayTimeUnit must be ns to avoid low-precision overlap |
| 35 | // calculations! |
| 36 | Out.objectBegin(); |
| 37 | Out.attribute("displayTimeUnit", "ns"); |
| 38 | Out.attributeBegin("traceEvents"); |
| 39 | Out.arrayBegin(); |
| 40 | rawEvent("M", llvm::json::Object{ |
| 41 | {"name", "process_name"}, |
| 42 | {"args", llvm::json::Object{{"name", "clangd"}}}, |
| 43 | }); |
| 44 | } |
| 45 | |
| 46 | ~JSONTracer() { |
| 47 | Out.arrayEnd(); |
| 48 | Out.attributeEnd(); |
| 49 | Out.objectEnd(); |
| 50 | Out.flush(); |
| 51 | } |
| 52 | |
| 53 | // We stash a Span object in the context. It will record the start/end, |
| 54 | // and this also allows us to look up the parent Span's information. |
| 55 | Context beginSpan( |
| 56 | llvm::StringRef Name, |
| 57 | llvm::function_ref<void(llvm::json::Object *)> AttachDetails) override { |
| 58 | auto JS = std::make_unique<JSONSpan>(this, Name); |
| 59 | AttachDetails(&JS->Args); |
| 60 | return Context::current().derive(SpanKey, std::move(JS)); |
| 61 | } |
| 62 | |
| 63 | // Trace viewer requires each thread to properly stack events. |
| 64 | // So we need to mark only duration that the span was active on the thread. |
| 65 | // (Hopefully any off-thread activity will be connected by a flow event). |
| 66 | // Record the end time here, but don't write the event: Args aren't ready yet. |
| 67 | void endSpan() override { |
| 68 | Context::current().getExisting(SpanKey)->markEnded(); |
| 69 | } |
| 70 | |
| 71 | void instant(llvm::StringRef Name, llvm::json::Object &&Args) override { |
| 72 | captureThreadMetadata(); |
| 73 | jsonEvent("i", |
| 74 | llvm::json::Object{{"name", Name}, {"args", std::move(Args)}}); |
| 75 | } |
| 76 | |
| 77 | // Record an event on the current thread. ph, pid, tid, ts are set. |
| 78 | // Contents must be a list of the other JSON key/values. |
| 79 | void jsonEvent(llvm::StringRef Phase, llvm::json::Object &&Contents, |
| 80 | uint64_t TID = llvm::get_threadid(), double Timestamp = 0) { |
| 81 | Contents["ts"] = Timestamp ? Timestamp : timestamp(); |
| 82 | Contents["tid"] = int64_t(TID); |
| 83 | std::lock_guard<std::mutex> Lock(Mu); |
| 84 | rawEvent(Phase, Contents); |
| 85 | } |
| 86 | |
| 87 | private: |
| 88 | class JSONSpan { |
| 89 | public: |
| 90 | JSONSpan(JSONTracer *Tracer, llvm::StringRef Name) |
| 91 | : StartTime(Tracer->timestamp()), EndTime(0), Name(Name), |
| 92 | TID(llvm::get_threadid()), Tracer(Tracer) { |
| 93 | // ~JSONSpan() may run in a different thread, so we need to capture now. |
| 94 | Tracer->captureThreadMetadata(); |
| 95 | |
| 96 | // We don't record begin events here (and end events in the destructor) |
| 97 | // because B/E pairs have to appear in the right order, which is awkward. |
| 98 | // Instead we send the complete (X) event in the destructor. |
| 99 | |
| 100 | // If our parent was on a different thread, add an arrow to this span. |
| 101 | auto *Parent = Context::current().get(SpanKey); |
| 102 | if (Parent && *Parent && (*Parent)->TID != TID) { |
| 103 | // If the parent span ended already, then show this as "following" it. |
| 104 | // Otherwise show us as "parallel". |
| 105 | double OriginTime = (*Parent)->EndTime; |
| 106 | if (!OriginTime) |
| 107 | OriginTime = (*Parent)->StartTime; |
Value stored to 'OriginTime' is never read | |
| 108 | |
| 109 | auto FlowID = nextID(); |
| 110 | Tracer->jsonEvent( |
| 111 | "s", |
| 112 | llvm::json::Object{{"id", FlowID}, |
| 113 | {"name", "Context crosses threads"}, |
| 114 | {"cat", "mock_cat"}}, |
| 115 | (*Parent)->TID, (*Parent)->StartTime); |
| 116 | Tracer->jsonEvent( |
| 117 | "f", |
| 118 | llvm::json::Object{{"id", FlowID}, |
| 119 | {"bp", "e"}, |
| 120 | {"name", "Context crosses threads"}, |
| 121 | {"cat", "mock_cat"}}, |
| 122 | TID); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | ~JSONSpan() { |
| 127 | // Finally, record the event (ending at EndTime, not timestamp())! |
| 128 | Tracer->jsonEvent("X", |
| 129 | llvm::json::Object{{"name", std::move(Name)}, |
| 130 | {"args", std::move(Args)}, |
| 131 | {"dur", EndTime - StartTime}}, |
| 132 | TID, StartTime); |
| 133 | } |
| 134 | |
| 135 | // May be called by any thread. |
| 136 | void markEnded() { EndTime = Tracer->timestamp(); } |
| 137 | |
| 138 | llvm::json::Object Args; |
| 139 | |
| 140 | private: |
| 141 | static int64_t nextID() { |
| 142 | static std::atomic<int64_t> Next = {0}; |
| 143 | return Next++; |
| 144 | } |
| 145 | |
| 146 | double StartTime; |
| 147 | std::atomic<double> EndTime; // Filled in by markEnded(). |
| 148 | std::string Name; |
| 149 | uint64_t TID; |
| 150 | JSONTracer *Tracer; |
| 151 | }; |
| 152 | static Key<std::unique_ptr<JSONSpan>> SpanKey; |
| 153 | |
| 154 | // Record an event. ph and pid are set. |
| 155 | // Contents must be a list of the other JSON key/values. |
| 156 | void rawEvent(llvm::StringRef Phase, |
| 157 | const llvm::json::Object &Event) /*REQUIRES(Mu)*/ { |
| 158 | // PID 0 represents the clangd process. |
| 159 | Out.object([&] { |
| 160 | Out.attribute("pid", 0); |
| 161 | Out.attribute("ph", Phase); |
| 162 | for (const auto &KV : Event) |
| 163 | Out.attribute(KV.first, KV.second); |
| 164 | }); |
| 165 | } |
| 166 | |
| 167 | // If we haven't already, emit metadata describing this thread. |
| 168 | void captureThreadMetadata() { |
| 169 | uint64_t TID = llvm::get_threadid(); |
| 170 | std::lock_guard<std::mutex> Lock(Mu); |
| 171 | if (ThreadsWithMD.insert(TID).second) { |
| 172 | llvm::SmallString<32> Name; |
| 173 | llvm::get_thread_name(Name); |
| 174 | if (!Name.empty()) { |
| 175 | rawEvent("M", llvm::json::Object{ |
| 176 | {"tid", int64_t(TID)}, |
| 177 | {"name", "thread_name"}, |
| 178 | {"args", llvm::json::Object{{"name", Name}}}, |
| 179 | }); |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | double timestamp() { |
| 185 | using namespace std::chrono; |
| 186 | return duration<double, std::micro>(system_clock::now() - Start).count(); |
| 187 | } |
| 188 | |
| 189 | std::mutex Mu; |
| 190 | llvm::json::OStream Out /*GUARDED_BY(Mu)*/; |
| 191 | llvm::DenseSet<uint64_t> ThreadsWithMD /*GUARDED_BY(Mu)*/; |
| 192 | const llvm::sys::TimePoint<> Start; |
| 193 | }; |
| 194 | |
| 195 | // We emit CSV as specified in RFC 4180: https://www.ietf.org/rfc/rfc4180.txt. |
| 196 | // \r\n line endings are used, cells with \r\n," are quoted, quotes are doubled. |
| 197 | class CSVMetricTracer : public EventTracer { |
| 198 | public: |
| 199 | CSVMetricTracer(llvm::raw_ostream &Out) : Out(Out) { |
| 200 | Start = std::chrono::steady_clock::now(); |
| 201 | |
| 202 | Out.SetUnbuffered(); // We write each line atomically. |
| 203 | Out << "Kind,Metric,Label,Value,Timestamp\r\n"; |
| 204 | } |
| 205 | |
| 206 | void record(const Metric &Metric, double Value, |
| 207 | llvm::StringRef Label) override { |
| 208 | assert(!needsQuote(Metric.Name))(static_cast <bool> (!needsQuote(Metric.Name)) ? void ( 0) : __assert_fail ("!needsQuote(Metric.Name)", "clang-tools-extra/clangd/support/Trace.cpp" , 208, __extension__ __PRETTY_FUNCTION__)); |
| 209 | std::string QuotedLabel; |
| 210 | if (needsQuote(Label)) |
| 211 | Label = QuotedLabel = quote(Label); |
| 212 | uint64_t Micros = std::chrono::duration_cast<std::chrono::microseconds>( |
| 213 | std::chrono::steady_clock::now() - Start) |
| 214 | .count(); |
| 215 | std::lock_guard<std::mutex> Lock(Mu); |
| 216 | Out << llvm::formatv("{0},{1},{2},{3:e},{4}.{5:6}\r\n", |
| 217 | typeName(Metric.Type), Metric.Name, Label, Value, |
| 218 | Micros / 1000000, Micros % 1000000); |
| 219 | } |
| 220 | |
| 221 | private: |
| 222 | llvm::StringRef typeName(Metric::MetricType T) { |
| 223 | switch (T) { |
| 224 | case Metric::Value: |
| 225 | return "v"; |
| 226 | case Metric::Counter: |
| 227 | return "c"; |
| 228 | case Metric::Distribution: |
| 229 | return "d"; |
| 230 | } |
| 231 | llvm_unreachable("Unknown Metric::MetricType enum")::llvm::llvm_unreachable_internal("Unknown Metric::MetricType enum" , "clang-tools-extra/clangd/support/Trace.cpp", 231); |
| 232 | } |
| 233 | |
| 234 | static bool needsQuote(llvm::StringRef Text) { |
| 235 | // https://www.ietf.org/rfc/rfc4180.txt section 2.6 |
| 236 | return Text.find_first_of(",\"\r\n") != llvm::StringRef::npos; |
| 237 | } |
| 238 | |
| 239 | std::string quote(llvm::StringRef Text) { |
| 240 | std::string Result = "\""; |
| 241 | for (char C : Text) { |
| 242 | Result.push_back(C); |
| 243 | if (C == '"') |
| 244 | Result.push_back('"'); |
| 245 | } |
| 246 | Result.push_back('"'); |
| 247 | return Result; |
| 248 | } |
| 249 | |
| 250 | private: |
| 251 | std::mutex Mu; |
| 252 | llvm::raw_ostream &Out /*GUARDED_BY(Mu)*/; |
| 253 | std::chrono::steady_clock::time_point Start; |
| 254 | }; |
| 255 | |
| 256 | Key<std::unique_ptr<JSONTracer::JSONSpan>> JSONTracer::SpanKey; |
| 257 | |
| 258 | EventTracer *T = nullptr; |
| 259 | } // namespace |
| 260 | |
| 261 | Session::Session(EventTracer &Tracer) { |
| 262 | assert(!T && "Resetting global tracer is not allowed.")(static_cast <bool> (!T && "Resetting global tracer is not allowed." ) ? void (0) : __assert_fail ("!T && \"Resetting global tracer is not allowed.\"" , "clang-tools-extra/clangd/support/Trace.cpp", 262, __extension__ __PRETTY_FUNCTION__)); |
| 263 | T = &Tracer; |
| 264 | } |
| 265 | |
| 266 | Session::~Session() { T = nullptr; } |
| 267 | |
| 268 | std::unique_ptr<EventTracer> createJSONTracer(llvm::raw_ostream &OS, |
| 269 | bool Pretty) { |
| 270 | return std::make_unique<JSONTracer>(OS, Pretty); |
| 271 | } |
| 272 | |
| 273 | std::unique_ptr<EventTracer> createCSVMetricTracer(llvm::raw_ostream &OS) { |
| 274 | return std::make_unique<CSVMetricTracer>(OS); |
| 275 | } |
| 276 | |
| 277 | void log(const llvm::Twine &Message) { |
| 278 | if (!T) |
| 279 | return; |
| 280 | T->instant("Log", llvm::json::Object{{"Message", Message.str()}}); |
| 281 | } |
| 282 | |
| 283 | bool enabled() { return T != nullptr; } |
| 284 | |
| 285 | // The JSON object is event args (owned by context), if the tracer wants them. |
| 286 | static std::pair<Context, llvm::json::Object *> |
| 287 | makeSpanContext(llvm::Twine Name, const Metric &LatencyMetric) { |
| 288 | if (!T) |
| 289 | return std::make_pair(Context::current().clone(), nullptr); |
| 290 | std::optional<WithContextValue> WithLatency; |
| 291 | using Clock = std::chrono::high_resolution_clock; |
| 292 | WithLatency.emplace(llvm::make_scope_exit( |
| 293 | [StartTime = Clock::now(), Name = Name.str(), &LatencyMetric] { |
| 294 | LatencyMetric.record( |
| 295 | std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - |
| 296 | StartTime) |
| 297 | .count(), |
| 298 | Name); |
| 299 | })); |
| 300 | llvm::json::Object *Args = nullptr; |
| 301 | Context Ctx = T->beginSpan( |
| 302 | Name.isSingleStringRef() ? Name.getSingleStringRef() |
| 303 | : llvm::StringRef(Name.str()), |
| 304 | [&](llvm::json::Object *A) { |
| 305 | assert(A && A->empty() && "Invalid AttachDetails() placeholder!")(static_cast <bool> (A && A->empty() && "Invalid AttachDetails() placeholder!") ? void (0) : __assert_fail ("A && A->empty() && \"Invalid AttachDetails() placeholder!\"" , "clang-tools-extra/clangd/support/Trace.cpp", 305, __extension__ __PRETTY_FUNCTION__)); |
| 306 | Args = A; |
| 307 | }); |
| 308 | return std::make_pair(std::move(Ctx), Args); |
| 309 | } |
| 310 | |
| 311 | // Fallback metric that measures latencies for spans without an explicit latency |
| 312 | // metric. Labels are span names. |
| 313 | constexpr Metric SpanLatency("span_latency", Metric::Distribution, "span_name"); |
| 314 | |
| 315 | // Span keeps a non-owning pointer to the args, which is how users access them. |
| 316 | // The args are owned by the context though. They stick around until the |
| 317 | // beginSpan() context is destroyed, when the tracing engine will consume them. |
| 318 | Span::Span(llvm::Twine Name) : Span(Name, SpanLatency) {} |
| 319 | Span::Span(llvm::Twine Name, const Metric &LatencyMetric) |
| 320 | : Span(makeSpanContext(Name, LatencyMetric)) {} |
| 321 | Span::Span(std::pair<Context, llvm::json::Object *> Pair) |
| 322 | : Args(Pair.second), RestoreCtx(std::move(Pair.first)) {} |
| 323 | |
| 324 | Span::~Span() { |
| 325 | if (T) |
| 326 | T->endSpan(); |
| 327 | } |
| 328 | |
| 329 | void Metric::record(double Value, llvm::StringRef Label) const { |
| 330 | if (!T) |
| 331 | return; |
| 332 | assert((LabelName.empty() == Label.empty()) &&(static_cast <bool> ((LabelName.empty() == Label.empty( )) && "recording a measurement with inconsistent labeling" ) ? void (0) : __assert_fail ("(LabelName.empty() == Label.empty()) && \"recording a measurement with inconsistent labeling\"" , "clang-tools-extra/clangd/support/Trace.cpp", 333, __extension__ __PRETTY_FUNCTION__)) |
| 333 | "recording a measurement with inconsistent labeling")(static_cast <bool> ((LabelName.empty() == Label.empty( )) && "recording a measurement with inconsistent labeling" ) ? void (0) : __assert_fail ("(LabelName.empty() == Label.empty()) && \"recording a measurement with inconsistent labeling\"" , "clang-tools-extra/clangd/support/Trace.cpp", 333, __extension__ __PRETTY_FUNCTION__)); |
| 334 | T->record(*this, Value, Label); |
| 335 | } |
| 336 | |
| 337 | Context EventTracer::beginSpan( |
| 338 | llvm::StringRef Name, |
| 339 | llvm::function_ref<void(llvm::json::Object *)> AttachDetails) { |
| 340 | return Context::current().clone(); |
| 341 | } |
| 342 | } // namespace trace |
| 343 | } // namespace clangd |
| 344 | } // namespace clang |