blob: 1d518ded80c904633569ac41a3f2f479bd1ddeed [file] [log] [blame]
//===--- 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);
}
}