| //===--- tracer.cpp -------------------------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "sourcekitd/Internal-XPC.h" |
| #include "sourcekitd/XpcTracing.h" |
| |
| #include "SourceKit/Support/Logging.h" |
| #include "SourceKit/Support/Concurrency.h" |
| |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Mutex.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include "llvm/Support/TimeValue.h" |
| #include "llvm/Support/YAMLTraits.h" |
| |
| #include <xpc/xpc.h> |
| |
| #include <algorithm> |
| #include <deque> |
| #include <functional> |
| #include <iomanip> |
| #include <map> |
| #include <set> |
| |
| using namespace llvm; |
| using namespace sourcekitd; |
| |
| |
| |
| //----------------------------------------------------------------------------// |
| // Generic |
| //----------------------------------------------------------------------------// |
| |
| static std::string trace_root_dir; |
| |
| bool isTracingEnabled() { |
| return !trace_root_dir.empty(); |
| } |
| |
| typedef SmallString<256> path_t; |
| static llvm::sys::Mutex FileOpMutex; |
| |
| static void fsEnsureDirExists(const char *Dir) { |
| llvm::sys::ScopedLock L(FileOpMutex); |
| if (!sys::fs::exists(Dir)) { |
| sys::fs::create_directory(Dir); |
| } |
| } |
| |
| static void fsWriteFile(const char *Path, StringRef Text) { |
| llvm::sys::ScopedLock L(FileOpMutex); |
| if (!sys::fs::exists(Path)) { |
| std::error_code EC; |
| raw_fd_ostream Outfile(Path, EC, sys::fs::F_Text); |
| Outfile << Text; |
| Outfile.close(); |
| } |
| } |
| |
| static void fsInitTraceRoot(path_t &RootDir, uint64_t Id) { |
| RootDir = trace_root_dir; |
| std::string DirName = llvm::sys::TimeValue::now().str(); |
| std::for_each(DirName.begin(), DirName.end(), |
| [] (char &C) { if (!isalnum(C)) C = '-'; }); |
| DirName += '-'; |
| DirName += std::to_string(Id); |
| sys::path::append(RootDir, DirName); |
| } |
| |
| static void fsAddFileWithRevision(path_t &Path, |
| const std::string &File, |
| uint64_t Id) { |
| sys::path::append(Path, |
| sys::path::stem(File) + |
| "." + |
| std::to_string(Id) + |
| sys::path::extension(File)); |
| } |
| |
| typedef SourceKit::trace::OperationKind OperationKind; |
| |
| struct OperationInfo { |
| sys::TimeValue StartedAt; |
| OperationKind Kind; |
| std::string SwiftArgs; |
| trace::StringPairs OpArgs; |
| trace::StringPairs Files; |
| |
| OperationInfo(OperationKind K, |
| std::string &&SwiftArgs, |
| trace::StringPairs &&Files, |
| trace::StringPairs &&OpArgs) |
| : StartedAt(sys::TimeValue::now()), |
| Kind(K), SwiftArgs(SwiftArgs), OpArgs(OpArgs), Files(Files) {} |
| |
| OperationInfo() = default; |
| OperationInfo(OperationInfo &&) = default; |
| OperationInfo &operator=(OperationInfo &&) = default; |
| |
| OperationInfo(const OperationInfo &) = delete; |
| OperationInfo &operator=(const OperationInfo &) = delete; |
| }; |
| |
| struct OpRec { |
| std::string StartedAt; |
| std::string OpName; |
| std::string SwiftArgs; |
| trace::StringPairs OpArgs; |
| trace::StringPairs FileMap; |
| }; |
| |
| template <typename U> |
| struct llvm::yaml::SequenceTraits<std::vector<U>> { |
| static size_t size(IO &Io, std::vector<U> &Vec) { |
| return Vec.size(); |
| } |
| static U &element(IO &Io, std::vector<U> &Vec, size_t Index) { |
| return Vec[Index]; |
| } |
| }; |
| |
| template <> |
| struct llvm::yaml::MappingTraits<OpRec> { |
| static void mapping(IO &Io, OpRec &Rec) { |
| Io.mapRequired("op", Rec.OpName); |
| Io.mapRequired("swift-args", Rec.SwiftArgs); |
| Io.mapRequired("op-args", Rec.OpArgs); |
| Io.mapRequired("file-map", Rec.FileMap); |
| } |
| }; |
| |
| template <typename U, typename V> |
| struct llvm::yaml::MappingTraits<std::pair<U, V>> { |
| static void mapping(IO &Io, std::pair<U, V> &Pair) { |
| Io.mapRequired("first", Pair.first); |
| Io.mapRequired("second", Pair.second); |
| } |
| }; |
| |
| |
| |
| //----------------------------------------------------------------------------// |
| // State |
| //----------------------------------------------------------------------------// |
| |
| class State { |
| typedef std::map<uint64_t, OperationInfo> OperationsType; |
| |
| static sys::Mutex GlobalMutex; |
| sys::Mutex LocalMutex; |
| |
| static std::shared_ptr<State> CurrentState; |
| static SourceKit::WorkQueue Queue; |
| static std::string CompArgsFile; |
| static std::string ActiveOpFile; |
| |
| const uint64_t Id; |
| std::atomic<uint64_t> UniqueId; |
| std::string TraceDir; |
| |
| std::set<uint64_t> ReportedOps; |
| std::set<uint64_t> SeenOps; |
| OperationsType Operations; |
| |
| State(uint64_t Id) : Id(Id), UniqueId(0) { |
| } |
| |
| void persist(); |
| |
| static std::shared_ptr<State> getState(uint64_t Session) { |
| llvm::sys::ScopedLock L(GlobalMutex); |
| |
| if (!CurrentState) { |
| CurrentState.reset(new State(Session)); |
| } else if (!CurrentState || CurrentState->Id != Session) { |
| auto S = CurrentState; |
| Queue.dispatch([S] {S->persist();}); |
| CurrentState.reset(new State(Session)); |
| } |
| return CurrentState; |
| } |
| |
| public: |
| State(const State &) = delete; |
| State &operator=(const State &) = delete; |
| State(State &&) = delete; |
| State &operator=(State &&) = delete; |
| |
| static void addOperation(uint64_t Session, |
| uint64_t OpId, |
| OperationKind Kind, |
| std::string &&SwiftArgs, |
| trace::StringPairs &&Files, |
| trace::StringPairs &&OpArgs) { |
| auto S = getState(Session); |
| llvm::sys::ScopedLock L(S->LocalMutex); |
| assert(S->SeenOps.find(OpId) == S->SeenOps.end()); |
| S->Operations[OpId] = OperationInfo( |
| Kind, std::move(SwiftArgs), std::move(Files), std::move(OpArgs)); |
| S->SeenOps.insert(OpId); |
| } |
| |
| static void removeOperation(uint64_t Session, |
| uint64_t OpId) { |
| auto S = getState(Session); |
| llvm::sys::ScopedLock L(S->LocalMutex); |
| assert(S->Operations.find(OpId) != S->Operations.end()); |
| S->Operations.erase(OpId); |
| } |
| |
| static void persistAsync(bool WithActions) { |
| llvm::sys::ScopedLock L(GlobalMutex); |
| auto S = CurrentState; |
| Queue.dispatch([S] {S->persist();}); |
| } |
| }; |
| |
| std::shared_ptr<State> State::CurrentState; |
| sys::Mutex State::GlobalMutex; |
| std::string State::CompArgsFile("swift-args.txt"); |
| std::string State::ActiveOpFile("active-operation.txt"); |
| |
| SourceKit::WorkQueue State::Queue { |
| SourceKit::WorkQueue::Dequeuing::Serial, "sourcekit.swift.tracer.state" |
| }; |
| |
| static std::map<OperationKind, std::string> op_kind_names { |
| std::make_pair(OperationKind::SimpleParse, "SimpleParse"), |
| std::make_pair(OperationKind::PerformSema, "PerformSema"), |
| std::make_pair(OperationKind::AnnotAndDiag, "AnnotAndDiag"), |
| std::make_pair(OperationKind::OpenInterface, "OpenInterface"), |
| std::make_pair(OperationKind::ReadDiagnostics, "ReadDiagnostics"), |
| std::make_pair(OperationKind::ReadSemanticInfo, "ReadSemanticInfo"), |
| std::make_pair(OperationKind::ReadSyntaxInfo, "ReadSyntaxInfo"), |
| std::make_pair(OperationKind::IndexModule, "IndexModule"), |
| std::make_pair(OperationKind::IndexSource, "IndexSource"), |
| std::make_pair(OperationKind::CursorInfoForIFaceGen, "CursorInfoForIFaceGen"), |
| std::make_pair(OperationKind::CursorInfoForSource, "CursorInfoForSource"), |
| std::make_pair(OperationKind::RelatedIdents, "RelatedIdents"), |
| std::make_pair(OperationKind::FormatText, "FormatText"), |
| std::make_pair(OperationKind::ExpandPlaceholder, "ExpandPlaceholder"), |
| std::make_pair(OperationKind::CodeCompletion, "CodeCompletion"), |
| std::make_pair(OperationKind::CodeCompletionInit, "CodeCompletionInit"), |
| }; |
| |
| void State::persist() { |
| llvm::sys::ScopedLock L(LocalMutex); |
| |
| if (TraceDir.empty()) { |
| path_t RootDir; |
| fsInitTraceRoot(RootDir, Id); |
| TraceDir = RootDir.c_str(); |
| } |
| fsEnsureDirExists(TraceDir.c_str()); |
| |
| path_t FilePath; |
| |
| std::for_each( |
| Operations.begin(), Operations.end(), |
| [&] (OperationsType::value_type &Pair) { |
| if (ReportedOps.find(Pair.first) == ReportedOps.end()) { |
| ReportedOps.insert(Pair.first); |
| |
| auto &Op = Pair.second; |
| |
| OpRec Rec; |
| Rec.OpName = op_kind_names[Op.Kind]; |
| Rec.OpArgs = Op.OpArgs; |
| |
| // Dump Swift arguments |
| FilePath = TraceDir; |
| fsAddFileWithRevision(FilePath, CompArgsFile, ++UniqueId); |
| Rec.SwiftArgs = FilePath.c_str(); |
| fsWriteFile(FilePath.c_str(), Op.SwiftArgs); |
| |
| // Dump files |
| std::for_each( |
| Op.Files.begin(), Op.Files.end(), |
| [&] (trace::StringPairs::value_type &Pair) { |
| FilePath = TraceDir; |
| fsAddFileWithRevision(FilePath, Pair.first, ++UniqueId); |
| Rec.FileMap.push_back(std::make_pair(Pair.first, FilePath.c_str())); |
| fsWriteFile(FilePath.c_str(), Pair.second); |
| }); |
| |
| // Serialize operation info |
| FilePath = TraceDir; |
| fsAddFileWithRevision(FilePath, ActiveOpFile, Pair.first); |
| std::error_code EC; |
| raw_fd_ostream Outfile(FilePath, EC, sys::fs::F_Text); |
| llvm::yaml::Output YamlOutput(Outfile); |
| YamlOutput << Rec; |
| } |
| }); |
| } |
| |
| |
| |
| //----------------------------------------------------------------------------// |
| // Init & trace |
| //----------------------------------------------------------------------------// |
| |
| void initializeTracing() { |
| const char *EnvOpt = ::getenv("SOURCEKIT_TRACE_ROOT"); |
| if (EnvOpt) { |
| using namespace SourceKit; |
| LOG_WARN_FUNC("TRACE: Enabled with root: " << EnvOpt); |
| trace_root_dir = EnvOpt; |
| } |
| } |
| |
| static uint32_t readUint64(xpc_object_t Arr, size_t Index) { |
| return xpc_array_get_uint64(Arr, Index); |
| } |
| |
| static trace::ActionKind readAction(xpc_object_t Arr, size_t Index) { |
| return static_cast<trace::ActionKind>(readUint64(Arr, Index)); |
| } |
| |
| static trace::OperationKind readOpKind(xpc_object_t Arr, size_t Index) { |
| return static_cast<trace::OperationKind>(readUint64(Arr, Index)); |
| } |
| |
| static llvm::StringRef readString(xpc_object_t Arr, size_t Index) { |
| return xpc_array_get_string(Arr, Index); |
| } |
| |
| static trace::StringPairs readStringPairs(xpc_object_t Arr, size_t &Index) { |
| trace::StringPairs Files; |
| auto Count = readUint64(Arr, Index++); |
| for (uint64_t I = 0; I < Count; I++) { |
| auto FileName = readString(Arr, Index++); |
| auto Text = readString(Arr, Index++); |
| Files.push_back(std::make_pair(FileName, Text)); |
| } |
| return Files; |
| } |
| |
| void handleTraceMessageRequest(uint64_t Session, xpc_object_t Msg) { |
| if (isTracingEnabled()) { |
| if (xpc_get_type(Msg) == XPC_TYPE_ARRAY) { |
| switch (readAction(Msg, 0)) { |
| |
| case trace::ActionKind::OperationStarted: { |
| size_t Index = 4; |
| auto Files = readStringPairs(Msg, Index); |
| auto OpArgs = readStringPairs(Msg, Index); |
| State::addOperation(Session, |
| readUint64(Msg, 1), |
| readOpKind(Msg, 2), |
| readString(Msg, 3), |
| std::move(Files), |
| std::move(OpArgs)); |
| break; |
| } |
| |
| case trace::ActionKind::OperationFinished: { |
| State::removeOperation(Session, readUint64(Msg, 1)); |
| break; |
| } |
| |
| } |
| |
| } else { |
| llvm::report_fatal_error("Unknown trace message"); |
| } |
| } |
| } |
| |
| void persistTracingData() { |
| if (isTracingEnabled()) { |
| State::persistAsync(true); |
| } |
| } |
| |