forked from lix-project/lix
tracing: wip i am going to regret not splitting this right now
Change-Id: I1b7f828127d69e8567f4faa96c4ace961cfc0be9
This commit is contained in:
parent
8610f42230
commit
87a8fa8f21
|
@ -41,7 +41,7 @@
|
||||||
project('lix', 'cpp',
|
project('lix', 'cpp',
|
||||||
version : run_command('bash', '-c', 'echo -n $(jq -r .version < ./version.json)$VERSION_SUFFIX', check : true).stdout().strip(),
|
version : run_command('bash', '-c', 'echo -n $(jq -r .version < ./version.json)$VERSION_SUFFIX', check : true).stdout().strip(),
|
||||||
default_options : [
|
default_options : [
|
||||||
'cpp_std=c++2a',
|
'cpp_std=c++23',
|
||||||
# TODO(Qyriad): increase the warning level
|
# TODO(Qyriad): increase the warning level
|
||||||
'warning_level=1',
|
'warning_level=1',
|
||||||
'debug=true',
|
'debug=true',
|
||||||
|
|
|
@ -18,10 +18,12 @@
|
||||||
#include "fetch-to-store.hh"
|
#include "fetch-to-store.hh"
|
||||||
#include "flake/flakeref.hh"
|
#include "flake/flakeref.hh"
|
||||||
#include "parser-tab.hh"
|
#include "parser-tab.hh"
|
||||||
|
#include "fast-function-tracer.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -394,6 +396,7 @@ EvalState::EvalState(
|
||||||
, debugStop(false)
|
, debugStop(false)
|
||||||
, trylevel(0)
|
, trylevel(0)
|
||||||
, regexCache(makeRegexCache())
|
, regexCache(makeRegexCache())
|
||||||
|
, tracer(nullptr)
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||||
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||||
|
@ -441,6 +444,11 @@ EvalState::EvalState(
|
||||||
}
|
}
|
||||||
|
|
||||||
createBaseEnv();
|
createBaseEnv();
|
||||||
|
|
||||||
|
// TODO: do this sensibly
|
||||||
|
if (auto traceFunctionsEnv = getEnv("NIX_TRACE_FUNCTIONS_FILE"); traceFunctionsEnv.has_value()) {
|
||||||
|
tracer = std::make_unique<FastFunctionTracer>(*this, *traceFunctionsEnv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1542,6 +1550,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
auto trace = evalSettings.traceFunctionCalls
|
auto trace = evalSettings.traceFunctionCalls
|
||||||
? std::make_unique<FunctionCallTrace>(positions[pos])
|
? std::make_unique<FunctionCallTrace>(positions[pos])
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
auto fastTraceTs = tracer ? std::optional{tracing::Timestamp::now()} : std::nullopt;
|
||||||
|
// TODO: this should never need to be a pointer to begin with! References are bad.
|
||||||
|
std::unique_ptr<FastFunctionTracer::Guard> fastTraceGuard = nullptr;
|
||||||
|
|
||||||
forceValue(fun, pos);
|
forceValue(fun, pos);
|
||||||
|
|
||||||
|
@ -1565,6 +1576,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
|
|
||||||
ExprLambda & lambda(*vCur.lambda.fun);
|
ExprLambda & lambda(*vCur.lambda.fun);
|
||||||
|
|
||||||
|
if (tracer && !fastTraceGuard) {
|
||||||
|
fastTraceGuard = std::make_unique<FastFunctionTracer::Guard>(tracer->enterFunction(lambda.name, lambda.pos, *fastTraceTs));
|
||||||
|
}
|
||||||
|
|
||||||
auto size =
|
auto size =
|
||||||
(!lambda.arg ? 0 : 1) +
|
(!lambda.arg ? 0 : 1) +
|
||||||
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
||||||
|
@ -1678,6 +1693,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
/* We have all the arguments, so call the primop. */
|
/* We have all the arguments, so call the primop. */
|
||||||
auto * fn = vCur.primOp;
|
auto * fn = vCur.primOp;
|
||||||
|
|
||||||
|
if (tracer && !fastTraceGuard) {
|
||||||
|
fastTraceGuard = std::make_unique<FastFunctionTracer::Guard>(tracer->enterFunction(symbols.create(fn->name), PosIdx(), *fastTraceTs));
|
||||||
|
}
|
||||||
|
|
||||||
nrPrimOpCalls++;
|
nrPrimOpCalls++;
|
||||||
if (countCalls) primOpCalls[fn->name]++;
|
if (countCalls) primOpCalls[fn->name]++;
|
||||||
|
|
||||||
|
@ -1712,7 +1731,6 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
} else {
|
} else {
|
||||||
/* We have all the arguments, so call the primop with
|
/* We have all the arguments, so call the primop with
|
||||||
the previous and new arguments. */
|
the previous and new arguments. */
|
||||||
|
|
||||||
Value * vArgs[maxPrimOpArity];
|
Value * vArgs[maxPrimOpArity];
|
||||||
auto n = argsDone;
|
auto n = argsDone;
|
||||||
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
|
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
|
||||||
|
@ -1725,6 +1743,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
nrPrimOpCalls++;
|
nrPrimOpCalls++;
|
||||||
if (countCalls) primOpCalls[fn->name]++;
|
if (countCalls) primOpCalls[fn->name]++;
|
||||||
|
|
||||||
|
if (tracer && !fastTraceGuard) {
|
||||||
|
fastTraceGuard = std::make_unique<FastFunctionTracer::Guard>(tracer->enterFunction(symbols.create(fn->name), PosIdx(), *fastTraceTs));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO:
|
// TODO:
|
||||||
// 1. Unify this and above code. Heavily redundant.
|
// 1. Unify this and above code. Heavily redundant.
|
||||||
|
@ -2760,6 +2782,12 @@ Expr * EvalState::parseExprFromFile(const SourcePath & path)
|
||||||
|
|
||||||
Expr * EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
|
Expr * EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
|
||||||
{
|
{
|
||||||
|
// TODO: turning these into symbols is kind of unfortunate because it leaks memory
|
||||||
|
// TODO: This API loses the category of the things!
|
||||||
|
std::optional<FastFunctionTracer::Guard> _g = tracer
|
||||||
|
? std::optional{tracer->enterFunction(symbols.create(path.to_string()), PosIdx())}
|
||||||
|
: std::nullopt;
|
||||||
|
|
||||||
auto buffer = path.readFile();
|
auto buffer = path.readFile();
|
||||||
// readFile hopefully have left some extra space for terminators
|
// readFile hopefully have left some extra space for terminators
|
||||||
buffer.append("\0\0", 2);
|
buffer.append("\0\0", 2);
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "repl-exit-status.hh"
|
#include "repl-exit-status.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -34,6 +35,7 @@ class StorePath;
|
||||||
struct SingleDerivedPath;
|
struct SingleDerivedPath;
|
||||||
enum RepairFlag : bool;
|
enum RepairFlag : bool;
|
||||||
|
|
||||||
|
class FastFunctionTracer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function that implements a primop.
|
* Function that implements a primop.
|
||||||
|
@ -274,6 +276,11 @@ private:
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<RegexCache> regexCache;
|
std::shared_ptr<RegexCache> regexCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function tracer. May be null!
|
||||||
|
*/
|
||||||
|
std::unique_ptr<FastFunctionTracer> tracer;
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
/**
|
/**
|
||||||
* Allocation cache for GC'd Value objects.
|
* Allocation cache for GC'd Value objects.
|
||||||
|
|
118
src/libexpr/fast-function-tracer.cc
Normal file
118
src/libexpr/fast-function-tracer.cc
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
#include "fast-function-tracer.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
#include "pos-idx.hh"
|
||||||
|
#include "symbol-table.hh"
|
||||||
|
#include "trace-writer.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
using std::operator""s;
|
||||||
|
|
||||||
|
class EvalStateRetriever : public tracing::InternedRetriever
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EvalState & evalState_;
|
||||||
|
|
||||||
|
EvalStateRetriever(EvalState & evalState) : evalState_(evalState) {}
|
||||||
|
|
||||||
|
virtual auto getName(tracing::InternedName idx) const -> std::string override;
|
||||||
|
virtual auto getPos(tracing::InternedPos idx, std::string const funcName) const
|
||||||
|
-> tracing::TracePos override;
|
||||||
|
|
||||||
|
virtual ~EvalStateRetriever() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FastFunctionTracer::Impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
tracing::TraceWriter writer_;
|
||||||
|
Path outputPath_;
|
||||||
|
tracing::TraceWriter::Track rootTrack_;
|
||||||
|
|
||||||
|
Impl(EvalState & evalState, Path outputFile)
|
||||||
|
: writer_{std::make_unique<EvalStateRetriever>(evalState)}
|
||||||
|
, outputPath_{outputFile}
|
||||||
|
, rootTrack_{writer_.trackByTid(getpid(), gettid(), "Functions")}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FastFunctionTracer::FastFunctionTracer(EvalState & evalState, Path outputFile)
|
||||||
|
: impl_{std::make_unique<Impl>(evalState, std::move(outputFile))}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FastFunctionTracer::~FastFunctionTracer()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
std::ofstream ofs(impl_->outputPath_);
|
||||||
|
auto buf = impl_->writer_.splat();
|
||||||
|
ofs.write(reinterpret_cast<const char *>(buf.data()), buf.size());
|
||||||
|
} catch (std::exception & e) {
|
||||||
|
std::cerr << "Exception ignored while writing trace file: " << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FastFunctionTracer::Guard
|
||||||
|
FastFunctionTracer::enterFunction(const Symbol sym, const PosIdx pos, tracing::Timestamp ts)
|
||||||
|
{
|
||||||
|
return Guard{*this, ts, sym, pos};
|
||||||
|
}
|
||||||
|
|
||||||
|
FastFunctionTracer::Guard::Guard(
|
||||||
|
FastFunctionTracer & tracer, tracing::Timestamp ts, const Symbol sym, const PosIdx idx
|
||||||
|
)
|
||||||
|
: tracer_(tracer)
|
||||||
|
{
|
||||||
|
tracer.impl_->rootTrack_.begin(tracing::InternedPos{idx.id}, tracing::InternedName{sym.id}, ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
FastFunctionTracer::Guard::~Guard()
|
||||||
|
{
|
||||||
|
tracer_.impl_->rootTrack_.end(tracing::Timestamp::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto EvalStateRetriever::getName(tracing::InternedName name) const -> std::string
|
||||||
|
{
|
||||||
|
if (name.key == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// XXX: This reaches into the internals of SymbolTable in order to be able
|
||||||
|
// to do its work while keeping libtracing decoupled from the precise
|
||||||
|
// implementation of Nix
|
||||||
|
return evalState_.symbols[Symbol(name.key)];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto EvalStateRetriever::getPos(tracing::InternedPos idx, std::string const funcName) const
|
||||||
|
-> tracing::TracePos
|
||||||
|
{
|
||||||
|
// XXX: This reaches into the internals of PosTable in order to be able
|
||||||
|
// to do its work while keeping libtracing decoupled from the precise
|
||||||
|
// implementation of Nix
|
||||||
|
//
|
||||||
|
// TODO: figure out a better way, maybe just giving it an opaque reference
|
||||||
|
// ideally without allocating
|
||||||
|
auto pos = evalState_.positions[PosIdx(idx.key)];
|
||||||
|
|
||||||
|
std::string out;
|
||||||
|
auto fileName = std::visit(
|
||||||
|
overloaded{
|
||||||
|
[](const std::monostate &) { return "«none»"s; },
|
||||||
|
[](const Pos::Stdin &) { return "«stdin»"s; },
|
||||||
|
[](const Pos::String & s) { return "«string»"s; },
|
||||||
|
[](const SourcePath & path) { return path.to_string(); }
|
||||||
|
},
|
||||||
|
pos.origin
|
||||||
|
);
|
||||||
|
|
||||||
|
return tracing::TracePos{
|
||||||
|
.fileName = fileName,
|
||||||
|
.functionName = funcName,
|
||||||
|
.lineNumber = pos.line,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
src/libexpr/fast-function-tracer.hh
Normal file
42
src/libexpr/fast-function-tracer.hh
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @brief This is a fast function tracer based on libtracing. It integrates
|
||||||
|
* with Nix's interning infrastructure to produce relatively reasonably
|
||||||
|
* processable traces compared to the existing function-trace.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "types.hh"
|
||||||
|
#include "trace-writer.hh"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
class Symbol;
|
||||||
|
class PosIdx;
|
||||||
|
class EvalState;
|
||||||
|
|
||||||
|
class FastFunctionTracer {
|
||||||
|
class Impl;
|
||||||
|
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** FIXME(threading): this is not thread safe */
|
||||||
|
class Guard {
|
||||||
|
FastFunctionTracer &tracer_;
|
||||||
|
explicit Guard(FastFunctionTracer &tracer, tracing::Timestamp ts, const Symbol sym, const PosIdx idx);
|
||||||
|
friend class FastFunctionTracer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~Guard();
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit FastFunctionTracer(EvalState &evalState, Path outputFile);
|
||||||
|
|
||||||
|
/** Get a guard that on destruction ends a trace-level scope (i.e. function call) */
|
||||||
|
Guard enterFunction(const Symbol sym, const PosIdx pos, tracing::Timestamp ts = tracing::Timestamp::now());
|
||||||
|
|
||||||
|
~FastFunctionTracer();
|
||||||
|
};
|
||||||
|
}
|
|
@ -71,6 +71,7 @@ libexpr_sources = files(
|
||||||
'eval-error.cc',
|
'eval-error.cc',
|
||||||
'eval-settings.cc',
|
'eval-settings.cc',
|
||||||
'eval.cc',
|
'eval.cc',
|
||||||
|
'fast-function-tracer.cc',
|
||||||
'function-trace.cc',
|
'function-trace.cc',
|
||||||
'get-drvs.cc',
|
'get-drvs.cc',
|
||||||
'json-to-value.cc',
|
'json-to-value.cc',
|
||||||
|
@ -102,6 +103,7 @@ libexpr_headers = files(
|
||||||
'eval-inline.hh',
|
'eval-inline.hh',
|
||||||
'eval-settings.hh',
|
'eval-settings.hh',
|
||||||
'eval.hh',
|
'eval.hh',
|
||||||
|
'fast-function-tracer.cc',
|
||||||
'flake/flake.hh',
|
'flake/flake.hh',
|
||||||
'flake/flakeref.hh',
|
'flake/flakeref.hh',
|
||||||
'flake/lockfile.hh',
|
'flake/lockfile.hh',
|
||||||
|
@ -133,6 +135,7 @@ libexpr = library(
|
||||||
lexer_tab,
|
lexer_tab,
|
||||||
libexpr_generated_headers,
|
libexpr_generated_headers,
|
||||||
dependencies : [
|
dependencies : [
|
||||||
|
liblixtracing,
|
||||||
liblixutil,
|
liblixutil,
|
||||||
liblixstore,
|
liblixstore,
|
||||||
liblixfetchers,
|
liblixfetchers,
|
||||||
|
|
|
@ -10,6 +10,10 @@ class PosIdx
|
||||||
friend struct LazyPosAcessors;
|
friend struct LazyPosAcessors;
|
||||||
friend class PosTable;
|
friend class PosTable;
|
||||||
|
|
||||||
|
// FastFunctionTracer internals
|
||||||
|
friend class EvalStateRetriever;
|
||||||
|
friend class FastFunctionTracer;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,10 @@ class Symbol
|
||||||
{
|
{
|
||||||
friend class SymbolTable;
|
friend class SymbolTable;
|
||||||
|
|
||||||
|
// FastFunctionTracer internals
|
||||||
|
friend class EvalStateRetriever;
|
||||||
|
friend class FastFunctionTracer;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#include "environment-variables.hh"
|
#include "environment-variables.hh"
|
||||||
#include "loggers.hh"
|
#include "loggers.hh"
|
||||||
|
#include "logging/chain-logger.hh"
|
||||||
|
#include "logging/trace-logger.hh"
|
||||||
#include "progress-bar.hh"
|
#include "progress-bar.hh"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ LogFormat parseLogFormat(const std::string & logFormatStr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Logger> makeDefaultLogger() {
|
std::shared_ptr<Logger> makeDefaultLogger() {
|
||||||
|
auto pickMainLogger = []() {
|
||||||
switch (defaultLogFormat) {
|
switch (defaultLogFormat) {
|
||||||
case LogFormat::raw:
|
case LogFormat::raw:
|
||||||
return makeSimpleLogger(false);
|
return makeSimpleLogger(false);
|
||||||
|
@ -38,6 +42,17 @@ std::shared_ptr<Logger> makeDefaultLogger() {
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto mainLogger = pickMainLogger();
|
||||||
|
|
||||||
|
// TODO: this is absolutely not the way to configure this
|
||||||
|
auto traceFile = getEnv("NIX_TRACE_FILE");
|
||||||
|
if (traceFile.has_value()) {
|
||||||
|
return std::shared_ptr<Logger>(std::make_shared<ChainLogger>(std::vector{mainLogger, std::shared_ptr<Logger>(std::make_shared<TraceLogger>(*traceFile))}));
|
||||||
|
} else {
|
||||||
|
return mainLogger;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLogFormat(const std::string & logFormatStr) {
|
void setLogFormat(const std::string & logFormatStr) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ class MyInternedRetriever : public InternedRetriever
|
||||||
return "meow";
|
return "meow";
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual auto getPos(InternedPos idx, std::string funcName) const -> TracePos override
|
virtual auto getPos(InternedPos idx, std::string const funcName) const -> TracePos override
|
||||||
{
|
{
|
||||||
return TracePos{
|
return TracePos{
|
||||||
.fileName = "file.nix",
|
.fileName = "file.nix",
|
||||||
|
@ -32,8 +32,10 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
nix::tracing::TraceWriter tw{std::make_unique<MyInternedRetriever>()};
|
nix::tracing::TraceWriter tw{std::make_unique<MyInternedRetriever>()};
|
||||||
|
|
||||||
tw.begin(InternedPos{1}, InternedName{3}, Timestamp{250});
|
auto track = tw.trackByTid(1, 2, "track");
|
||||||
tw.end(Timestamp{1235});
|
|
||||||
|
track.begin(InternedPos{1}, InternedName{3}, Timestamp{250});
|
||||||
|
track.end(Timestamp{1235});
|
||||||
|
|
||||||
std::ofstream ofs(argv[1]);
|
std::ofstream ofs(argv[1]);
|
||||||
auto buf = tw.splat();
|
auto buf = tw.splat();
|
||||||
|
|
|
@ -31,7 +31,7 @@ libtracing = library(
|
||||||
# FIXME(Qyriad): is this right?
|
# FIXME(Qyriad): is this right?
|
||||||
install_rpath : libdir,
|
install_rpath : libdir,
|
||||||
cpp_args : [ '-DPERFETTO_DISABLE_LOG' ],
|
cpp_args : [ '-DPERFETTO_DISABLE_LOG' ],
|
||||||
include_directories : include_directories('./perfetto/base/build_configs/bazel'),
|
include_directories : include_directories('./perfetto/base/build_configs/bazel')
|
||||||
)
|
)
|
||||||
|
|
||||||
liblixtracing = declare_dependency(
|
liblixtracing = declare_dependency(
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
#include "meow/protos/perfetto/trace/perfetto_trace.pbzero.h"
|
#include "meow/protos/perfetto/trace/perfetto_trace.pbzero.h"
|
||||||
#include "perfetto/protozero/message.h"
|
#include "perfetto/protozero/message.h"
|
||||||
#include "perfetto/protozero/message_handle.h"
|
#include "perfetto/protozero/message_handle.h"
|
||||||
#include "perfetto/protozero/scattered_heap_buffer.h"
|
#include "perfetto/protozero/scattered_heap_buffer.h"
|
||||||
#include "perfetto/protozero/scattered_stream_writer.h"
|
#include "perfetto/protozero/scattered_stream_writer.h"
|
||||||
#include "trace-writer.hh"
|
#include "trace-writer.hh"
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct std::hash<nix::tracing::InternedPos>
|
struct std::hash<nix::tracing::InternedPos>
|
||||||
|
@ -56,15 +61,27 @@ struct TraceWriter::Impl
|
||||||
Trace trace_;
|
Trace trace_;
|
||||||
protozero::MessageHandle<Trace> traceHandle_;
|
protozero::MessageHandle<Trace> traceHandle_;
|
||||||
|
|
||||||
std::unordered_map<InternedPos, ProtoInternedPos> posMap_;
|
std::unordered_map<InternedPos, ProtoInternedPos> posMap_{};
|
||||||
uint64_t posIid_ = 1;
|
uint64_t posIid_ = 1;
|
||||||
|
|
||||||
std::unordered_map<InternedName, ProtoInternedName> nameMap_;
|
std::unordered_map<InternedName, ProtoInternedName> nameMap_{};
|
||||||
uint64_t nameIid_ = 1;
|
uint64_t nameIid_ = 1;
|
||||||
|
|
||||||
auto getName(InternedName name, std::optional<std::string> &outName) -> ProtoInternedName;
|
uint64_t logMessageIid_ = 1;
|
||||||
|
|
||||||
auto getPos(InternedPos pos, InternedName funcName, std::optional<TracePos> &outPos) -> ProtoInternedPos;
|
/** Mapping between TID and track */
|
||||||
|
std::unordered_map<uint64_t, Track> tracks_{};
|
||||||
|
|
||||||
|
std::mt19937_64 rand_;
|
||||||
|
|
||||||
|
/** trusted_packet_sequence_id, which appears as the same value in every packet written by a
|
||||||
|
* writer */
|
||||||
|
uint64_t trustedSequenceId_;
|
||||||
|
|
||||||
|
auto getName(InternedName name, std::optional<std::string> & outName) -> ProtoInternedName;
|
||||||
|
|
||||||
|
auto getPos(InternedPos pos, InternedName funcName, std::optional<TracePos> & outPos)
|
||||||
|
-> ProtoInternedPos;
|
||||||
|
|
||||||
Impl(std::unique_ptr<InternedRetriever> retriever)
|
Impl(std::unique_ptr<InternedRetriever> retriever)
|
||||||
: retriever_(std::move(retriever))
|
: retriever_(std::move(retriever))
|
||||||
|
@ -74,19 +91,25 @@ struct TraceWriter::Impl
|
||||||
buf_.set_writer(&this->writer_);
|
buf_.set_writer(&this->writer_);
|
||||||
trace_.Reset(&this->writer_, &this->arena_);
|
trace_.Reset(&this->writer_, &this->arena_);
|
||||||
|
|
||||||
auto p = protozero::MessageHandle<TracePacket>(trace_.add_packet());
|
std::random_device rd;
|
||||||
auto td = protozero::MessageHandle<TrackDescriptor>(p->set_track_descriptor());
|
rand_ = std::mt19937_64(rd());
|
||||||
td->set_name("track");
|
|
||||||
td->set_uuid(1);
|
|
||||||
|
|
||||||
p->set_trusted_packet_sequence_id(1337);
|
trustedSequenceId_ = rand_();
|
||||||
p->set_sequence_flags(perfetto_pbzero_enum_TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
|
|
||||||
p->set_previous_packet_dropped(true);
|
|
||||||
p->set_first_packet_on_sequence(true);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto TraceWriter::Impl::getName(InternedName name, std::optional<std::string> &outName) -> ProtoInternedName
|
auto Timestamp::now() -> Timestamp
|
||||||
|
{
|
||||||
|
auto point = std::chrono::steady_clock::now();
|
||||||
|
return tracing::Timestamp{
|
||||||
|
.monotonic_nanos = static_cast<uint64_t>(
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(point.time_since_epoch()).count()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto TraceWriter::Impl::getName(InternedName name, std::optional<std::string> & outName)
|
||||||
|
-> ProtoInternedName
|
||||||
{
|
{
|
||||||
if (auto it = nameMap_.find(name); it != nameMap_.end()) {
|
if (auto it = nameMap_.find(name); it != nameMap_.end()) {
|
||||||
// one already there, return it
|
// one already there, return it
|
||||||
|
@ -101,7 +124,9 @@ auto TraceWriter::Impl::getName(InternedName name, std::optional<std::string> &o
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TraceWriter::Impl::getPos(InternedPos pos, InternedName funcName, std::optional<TracePos> &outPos) -> ProtoInternedPos
|
auto TraceWriter::Impl::getPos(
|
||||||
|
InternedPos pos, InternedName funcName, std::optional<TracePos> & outPos
|
||||||
|
) -> ProtoInternedPos
|
||||||
{
|
{
|
||||||
if (auto it = posMap_.find(pos); it != posMap_.end()) {
|
if (auto it = posMap_.find(pos); it != posMap_.end()) {
|
||||||
// one already there, return it
|
// one already there, return it
|
||||||
|
@ -122,18 +147,78 @@ TraceWriter::TraceWriter(std::unique_ptr<InternedRetriever> retriever)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TraceWriter::Track TraceWriter::trackByTid(uint64_t pid, uint64_t tid, std::string_view const name)
|
||||||
|
{
|
||||||
|
auto it = impl_->tracks_.find(tid);
|
||||||
|
if (it != impl_->tracks_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newTrack = Track(*this, pid, tid, name, std::nullopt, impl_->tracks_.empty());
|
||||||
|
impl_->tracks_.emplace(std::make_pair(tid, newTrack));
|
||||||
|
|
||||||
|
return newTrack;
|
||||||
|
}
|
||||||
|
|
||||||
TraceWriter::~TraceWriter() = default;
|
TraceWriter::~TraceWriter() = default;
|
||||||
|
|
||||||
void TraceWriter::begin(InternedPos pos, InternedName name, Timestamp ts)
|
TraceWriter::Track::Track(
|
||||||
|
TraceWriter & w,
|
||||||
|
std::optional<uint64_t> pid,
|
||||||
|
std::optional<uint64_t> tid,
|
||||||
|
const std::string_view name,
|
||||||
|
std::optional<uint64_t> parentUuid,
|
||||||
|
bool initial
|
||||||
|
)
|
||||||
|
: w_(w)
|
||||||
|
{
|
||||||
|
auto p = protozero::MessageHandle<TracePacket>(w_.impl_->trace_.add_packet());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto td = protozero::MessageHandle<TrackDescriptor>(p->set_track_descriptor());
|
||||||
|
|
||||||
|
td->set_name(name.data(), name.size());
|
||||||
|
uuid_ = w_.impl_->rand_();
|
||||||
|
td->set_uuid(uuid_);
|
||||||
|
|
||||||
|
if (parentUuid.has_value()) {
|
||||||
|
td->set_parent_uuid(*parentUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tid.has_value() && pid.has_value()) {
|
||||||
|
auto thread = protozero::MessageHandle<ThreadDescriptor>(td->set_thread());
|
||||||
|
thread->set_pid(*pid);
|
||||||
|
thread->set_tid(*tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p->set_trusted_packet_sequence_id(w_.impl_->trustedSequenceId_);
|
||||||
|
if (initial) {
|
||||||
|
p->set_sequence_flags(perfetto_pbzero_enum_TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
|
||||||
|
p->set_previous_packet_dropped(true);
|
||||||
|
p->set_first_packet_on_sequence(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto TraceWriter::Track::child(std::string_view const name) -> TraceWriter::Track
|
||||||
|
{
|
||||||
|
return Track(w_, std::nullopt, std::nullopt, name, uuid_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: Frustratingly, this has to be implemented twice, since you more or less
|
||||||
|
// otherwise have to invert control to do this reasonably while maintaining the
|
||||||
|
// sequencing invariants (that you can't write to the parent message while a
|
||||||
|
// child message is still open)
|
||||||
|
void TraceWriter::Track::begin(InternedPos pos, InternedName name, Timestamp ts)
|
||||||
{
|
{
|
||||||
|
|
||||||
std::optional<std::string> nameToIntern;
|
std::optional<std::string> nameToIntern;
|
||||||
std::optional<TracePos> posToIntern;
|
std::optional<TracePos> posToIntern;
|
||||||
|
|
||||||
auto nameIid = impl_->getName(name, nameToIntern);
|
auto nameIid = w_.impl_->getName(name, nameToIntern);
|
||||||
auto posIid = impl_->getPos(pos, name, posToIntern);
|
auto posIid = w_.impl_->getPos(pos, name, posToIntern);
|
||||||
|
|
||||||
auto packet = protozero::MessageHandle<TracePacket>(impl_->trace_.add_packet());
|
auto packet = protozero::MessageHandle<TracePacket>(w_.impl_->trace_.add_packet());
|
||||||
|
|
||||||
packet->set_sequence_flags(perfetto_pbzero_enum_TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
|
packet->set_sequence_flags(perfetto_pbzero_enum_TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
|
||||||
|
|
||||||
|
@ -147,7 +232,8 @@ void TraceWriter::begin(InternedPos pos, InternedName name, Timestamp ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (posToIntern.has_value()) {
|
if (posToIntern.has_value()) {
|
||||||
auto sourceLoc = protozero::MessageHandle<SourceLocation>(internedData->add_source_locations());
|
auto sourceLoc =
|
||||||
|
protozero::MessageHandle<SourceLocation>(internedData->add_source_locations());
|
||||||
sourceLoc->set_iid(posIid.key);
|
sourceLoc->set_iid(posIid.key);
|
||||||
sourceLoc->set_file_name(posToIntern->fileName);
|
sourceLoc->set_file_name(posToIntern->fileName);
|
||||||
sourceLoc->set_function_name(posToIntern->functionName);
|
sourceLoc->set_function_name(posToIntern->functionName);
|
||||||
|
@ -156,29 +242,108 @@ void TraceWriter::begin(InternedPos pos, InternedName name, Timestamp ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
packet->set_timestamp(ts.monotonic_nanos);
|
packet->set_timestamp(ts.monotonic_nanos);
|
||||||
packet->set_trusted_packet_sequence_id(1337);
|
packet->set_trusted_packet_sequence_id(w_.impl_->trustedSequenceId_);
|
||||||
|
|
||||||
|
{
|
||||||
auto te = protozero::MessageHandle<TrackEvent>(packet->set_track_event());
|
auto te = protozero::MessageHandle<TrackEvent>(packet->set_track_event());
|
||||||
te->set_type(perfetto_pbzero_enum_TrackEvent::TYPE_SLICE_BEGIN);
|
te->set_type(perfetto_pbzero_enum_TrackEvent::TYPE_SLICE_BEGIN);
|
||||||
te->set_name_iid(nameIid.key);
|
te->set_name_iid(nameIid.key);
|
||||||
te->set_source_location_iid(posIid.key);
|
te->set_source_location_iid(posIid.key);
|
||||||
|
|
||||||
// TODO: make a real value
|
te->set_track_uuid(uuid_);
|
||||||
te->set_track_uuid(1);
|
}
|
||||||
|
|
||||||
// te->set_name_iid(nameIid.key);
|
|
||||||
// te->set_name("meow");
|
|
||||||
// TODO: te->set_source_location_iid();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TraceWriter::end(Timestamp ts)
|
void TraceWriter::Track::begin(
|
||||||
|
std::string_view const name, std::optional<std::string_view> category, Timestamp ts
|
||||||
|
)
|
||||||
{
|
{
|
||||||
protozero::MessageHandle<TracePacket> tp(impl_->trace_.add_packet());
|
auto packet = protozero::MessageHandle<TracePacket>(w_.impl_->trace_.add_packet());
|
||||||
|
|
||||||
|
packet->set_timestamp(ts.monotonic_nanos);
|
||||||
|
packet->set_trusted_packet_sequence_id(w_.impl_->trustedSequenceId_);
|
||||||
|
auto te = protozero::MessageHandle<TrackEvent>(packet->set_track_event());
|
||||||
|
te->set_type(perfetto_pbzero_enum_TrackEvent::TYPE_SLICE_BEGIN);
|
||||||
|
te->set_name(name.data(), name.size());
|
||||||
|
if (category.has_value()) {
|
||||||
|
te->add_categories(category->data(), category->length());
|
||||||
|
}
|
||||||
|
|
||||||
|
te->set_track_uuid(uuid_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::Track::end(Timestamp ts)
|
||||||
|
{
|
||||||
|
protozero::MessageHandle<TracePacket> tp{w_.impl_->trace_.add_packet()};
|
||||||
tp->set_timestamp(ts.monotonic_nanos);
|
tp->set_timestamp(ts.monotonic_nanos);
|
||||||
tp->set_trusted_packet_sequence_id(1337);
|
tp->set_trusted_packet_sequence_id(w_.impl_->trustedSequenceId_);
|
||||||
|
|
||||||
protozero::MessageHandle<TrackEvent> te(tp->set_track_event());
|
protozero::MessageHandle<TrackEvent> te(tp->set_track_event());
|
||||||
te->set_type(perfetto_pbzero_enum_TrackEvent::TYPE_SLICE_END);
|
te->set_type(perfetto_pbzero_enum_TrackEvent::TYPE_SLICE_END);
|
||||||
te->set_track_uuid(1);
|
te->set_track_uuid(uuid_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** "Interns" a log message. That is to say, does not intern a log message and just sends it. */
|
||||||
|
static void internLogMessage(InternedData * id, uint64_t iid, std::string_view const message)
|
||||||
|
{
|
||||||
|
protozero::MessageHandle internedData{id};
|
||||||
|
|
||||||
|
{
|
||||||
|
protozero::MessageHandle body{internedData->add_log_message_body()};
|
||||||
|
body->set_body(message.data(), message.size());
|
||||||
|
body->set_iid(iid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr LogMessage_Priority mapLogLevel(LogLevel level)
|
||||||
|
{
|
||||||
|
switch (level) {
|
||||||
|
case LogLevel::VERBOSE:
|
||||||
|
return LogMessage_Priority::PRIO_VERBOSE;
|
||||||
|
case LogLevel::DEBUG:
|
||||||
|
return LogMessage_Priority::PRIO_DEBUG;
|
||||||
|
case LogLevel::INFO:
|
||||||
|
return LogMessage_Priority::PRIO_INFO;
|
||||||
|
case LogLevel::WARN:
|
||||||
|
return LogMessage_Priority::PRIO_WARN;
|
||||||
|
case LogLevel::ERROR:
|
||||||
|
return LogMessage_Priority::PRIO_ERROR;
|
||||||
|
case LogLevel::FATAL:
|
||||||
|
return LogMessage_Priority::PRIO_FATAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceWriter::Track::logMessage(
|
||||||
|
std::string_view const message,
|
||||||
|
std::optional<std::string_view> const category,
|
||||||
|
Timestamp ts,
|
||||||
|
LogLevel level
|
||||||
|
)
|
||||||
|
{
|
||||||
|
protozero::MessageHandle tp{w_.impl_->trace_.add_packet()};
|
||||||
|
tp->set_timestamp(ts.monotonic_nanos);
|
||||||
|
tp->set_trusted_packet_sequence_id(w_.impl_->trustedSequenceId_);
|
||||||
|
tp->set_sequence_flags(perfetto_pbzero_enum_TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
|
||||||
|
|
||||||
|
uint64_t iid = w_.impl_->logMessageIid_++;
|
||||||
|
internLogMessage(tp->set_interned_data(), iid, message);
|
||||||
|
|
||||||
|
{
|
||||||
|
protozero::MessageHandle<TrackEvent> te{tp->set_track_event()};
|
||||||
|
te->set_type(perfetto_pbzero_enum_TrackEvent::TYPE_INSTANT);
|
||||||
|
te->set_track_uuid(uuid_);
|
||||||
|
te->set_name(message.data(), message.size());
|
||||||
|
|
||||||
|
if (category.has_value()) {
|
||||||
|
te->add_categories(category->data(), category->length());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
protozero::MessageHandle<LogMessage> logMessage{te->set_log_message()};
|
||||||
|
logMessage->set_body_iid(iid);
|
||||||
|
logMessage->set_prio(mapLogLevel(level));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TraceWriter::splat() const -> std::vector<uint8_t>
|
auto TraceWriter::splat() const -> std::vector<uint8_t>
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace nix::tracing {
|
namespace nix::tracing {
|
||||||
|
|
||||||
|
enum class LogLevel {
|
||||||
|
VERBOSE,
|
||||||
|
DEBUG,
|
||||||
|
INFO,
|
||||||
|
WARN,
|
||||||
|
ERROR,
|
||||||
|
FATAL,
|
||||||
|
};
|
||||||
|
|
||||||
struct InternedPos
|
struct InternedPos
|
||||||
{
|
{
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
|
@ -29,6 +37,8 @@ struct InternedName
|
||||||
struct Timestamp
|
struct Timestamp
|
||||||
{
|
{
|
||||||
uint64_t monotonic_nanos;
|
uint64_t monotonic_nanos;
|
||||||
|
|
||||||
|
static auto now() -> Timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TracePos
|
struct TracePos
|
||||||
|
@ -43,7 +53,7 @@ struct TracePos
|
||||||
struct InternedRetriever
|
struct InternedRetriever
|
||||||
{
|
{
|
||||||
virtual auto getName(InternedName idx) const -> std::string = 0;
|
virtual auto getName(InternedName idx) const -> std::string = 0;
|
||||||
virtual auto getPos(InternedPos idx, std::string funcName) const -> TracePos = 0;
|
virtual auto getPos(InternedPos idx, std::string const funcName) const -> TracePos = 0;
|
||||||
|
|
||||||
virtual ~InternedRetriever() = default;
|
virtual ~InternedRetriever() = default;
|
||||||
};
|
};
|
||||||
|
@ -53,10 +63,55 @@ class TraceWriter
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl_;
|
std::unique_ptr<Impl> impl_;
|
||||||
public:
|
public:
|
||||||
|
class Track
|
||||||
|
{
|
||||||
|
TraceWriter & w_;
|
||||||
|
uint64_t uuid_;
|
||||||
|
|
||||||
|
explicit Track(
|
||||||
|
TraceWriter & w,
|
||||||
|
std::optional<uint64_t> pid,
|
||||||
|
std::optional<uint64_t> tid,
|
||||||
|
std::string_view const name,
|
||||||
|
std::optional<uint64_t> parentUuid,
|
||||||
|
bool initial = false
|
||||||
|
);
|
||||||
|
|
||||||
|
friend class TraceWriter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Begins an event on the track using interning machinery */
|
||||||
|
void begin(InternedPos pos, InternedName name, Timestamp ts);
|
||||||
|
|
||||||
|
/** Begins an event on the track, without using the interning machinery */
|
||||||
|
void begin(
|
||||||
|
std::string_view const name,
|
||||||
|
std::optional<std::string_view> const category,
|
||||||
|
Timestamp ts
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Ends the active event on this track */
|
||||||
|
void end(Timestamp ts);
|
||||||
|
|
||||||
|
/** Send a log message */
|
||||||
|
void logMessage(
|
||||||
|
std::string_view const message,
|
||||||
|
std::optional<std::string_view> const category,
|
||||||
|
Timestamp ts,
|
||||||
|
LogLevel level
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Makes a child track parented to this one
|
||||||
|
*
|
||||||
|
* This is useful for doing async traces (since async spans are just,
|
||||||
|
* you invent a track per event).
|
||||||
|
*/
|
||||||
|
Track child(std::string_view const name);
|
||||||
|
};
|
||||||
|
|
||||||
TraceWriter(std::unique_ptr<InternedRetriever> retriever);
|
TraceWriter(std::unique_ptr<InternedRetriever> retriever);
|
||||||
|
|
||||||
void begin(InternedPos pos, InternedName name, Timestamp ts);
|
Track trackByTid(uint64_t pid, uint64_t tid, std::string_view const name);
|
||||||
void end(Timestamp ts);
|
|
||||||
|
|
||||||
/** Splats the trace out into a buffer */
|
/** Splats the trace out into a buffer */
|
||||||
auto splat() const -> std::vector<uint8_t>;
|
auto splat() const -> std::vector<uint8_t>;
|
||||||
|
|
53
src/libutil/logging/chain-logger.cc
Normal file
53
src/libutil/logging/chain-logger.cc
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#include "logging/chain-logger.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
void ChainLogger::log(Verbosity level, std::string_view s)
|
||||||
|
{
|
||||||
|
for (auto && child : children_) {
|
||||||
|
child->log(level, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainLogger::logEI(const ErrorInfo &ei) {
|
||||||
|
for (auto && child : children_) {
|
||||||
|
child->logEI(ei);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainLogger::startActivity(
|
||||||
|
ActivityId act,
|
||||||
|
Verbosity level,
|
||||||
|
ActivityType type,
|
||||||
|
const std::string & s,
|
||||||
|
const Fields & fields,
|
||||||
|
ActivityId parent
|
||||||
|
)
|
||||||
|
{
|
||||||
|
for (auto && child : children_) {
|
||||||
|
child->startActivity(act, level, type, s, fields, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainLogger::stopActivity(ActivityId act)
|
||||||
|
{
|
||||||
|
for (auto && child : children_) {
|
||||||
|
child->stopActivity(act);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainLogger::result(ActivityId act, ResultType type, const Fields & fields)
|
||||||
|
{
|
||||||
|
for (auto && child : children_) {
|
||||||
|
child->result(act, type, fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainLogger::setPrintBuildLogs(bool printBuildLogs)
|
||||||
|
{
|
||||||
|
for (auto && child : children_) {
|
||||||
|
child->setPrintBuildLogs(printBuildLogs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
src/libutil/logging/chain-logger.hh
Normal file
40
src/libutil/logging/chain-logger.hh
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "logging.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/** Logs to a chain of other loggers */
|
||||||
|
class ChainLogger : public Logger
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<Logger>> children_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChainLogger(std::vector<std::shared_ptr<Logger>> children) : children_{std::move(children)} {}
|
||||||
|
|
||||||
|
virtual void log(Verbosity level, std::string_view s) override;
|
||||||
|
virtual void startActivity(
|
||||||
|
ActivityId act,
|
||||||
|
Verbosity level,
|
||||||
|
ActivityType type,
|
||||||
|
const std::string & s,
|
||||||
|
const Fields & fields,
|
||||||
|
ActivityId parent
|
||||||
|
) override;
|
||||||
|
virtual void stopActivity(ActivityId act) override;
|
||||||
|
virtual void result(ActivityId act, ResultType type, const Fields & fields) override;
|
||||||
|
|
||||||
|
virtual void logEI(const ErrorInfo &ei) override;
|
||||||
|
|
||||||
|
// FIXME: ask()
|
||||||
|
// It needs some API fixes to figure out if loggers even support it.
|
||||||
|
|
||||||
|
virtual void setPrintBuildLogs(bool printBuildLogs) override;
|
||||||
|
|
||||||
|
virtual ~ChainLogger() {}
|
||||||
|
};
|
||||||
|
}
|
232
src/libutil/logging/trace-logger.cc
Normal file
232
src/libutil/logging/trace-logger.cc
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "error.hh"
|
||||||
|
#include "logging.hh"
|
||||||
|
#include "trace-writer.hh"
|
||||||
|
#include "logging/trace-logger.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
using std::operator""sv;
|
||||||
|
|
||||||
|
struct FakeRetriever : tracing::InternedRetriever
|
||||||
|
{
|
||||||
|
virtual auto getName(tracing::InternedName idx) const -> std::string override
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual auto getPos(tracing::InternedPos idx, std::string const funcName) const
|
||||||
|
-> tracing::TracePos override
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TraceLogger::Impl
|
||||||
|
{
|
||||||
|
nix::Path outputPath_;
|
||||||
|
|
||||||
|
std::mutex lock_;
|
||||||
|
tracing::TraceWriter writer_;
|
||||||
|
std::unordered_map<ActivityId, tracing::TraceWriter::Track> activityToTrack_;
|
||||||
|
|
||||||
|
tracing::TraceWriter::Track mainTrack_;
|
||||||
|
|
||||||
|
Impl(nix::Path outputPath)
|
||||||
|
: outputPath_(outputPath)
|
||||||
|
, writer_(std::make_unique<FakeRetriever>())
|
||||||
|
, mainTrack_(writer_.trackByTid(getpid(), gettid(), "Lix"))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TraceLogger::TraceLogger(const nix::Path outputPath)
|
||||||
|
: impl_{std::make_unique<TraceLogger::Impl>(outputPath)}
|
||||||
|
{
|
||||||
|
std::cerr << "TraceLogger!!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceLogger::~TraceLogger()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
std::ofstream ofs(impl_->outputPath_);
|
||||||
|
auto buf = impl_->writer_.splat();
|
||||||
|
ofs.write(reinterpret_cast<const char *>(buf.data()), buf.size());
|
||||||
|
} catch (std::exception & e) {
|
||||||
|
std::cerr << "Exception ignored while writing trace file: " << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static tracing::LogLevel mapLogLevel(Verbosity v)
|
||||||
|
{
|
||||||
|
switch (v) {
|
||||||
|
case lvlError:
|
||||||
|
return tracing::LogLevel::ERROR;
|
||||||
|
case lvlWarn:
|
||||||
|
return tracing::LogLevel::WARN;
|
||||||
|
case lvlNotice:
|
||||||
|
case lvlInfo:
|
||||||
|
return tracing::LogLevel::INFO;
|
||||||
|
case lvlTalkative:
|
||||||
|
return tracing::LogLevel::DEBUG;
|
||||||
|
case lvlChatty:
|
||||||
|
case lvlDebug:
|
||||||
|
case lvlVomit:
|
||||||
|
return tracing::LogLevel::VERBOSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceLogger::log(Verbosity level, std::string_view s)
|
||||||
|
{
|
||||||
|
std::lock_guard _g{impl_->lock_};
|
||||||
|
impl_->mainTrack_.logMessage(s, "logs"sv, tracing::Timestamp::now(), mapLogLevel(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceLogger::logEI(const ErrorInfo & ei)
|
||||||
|
{
|
||||||
|
std::stringstream oss;
|
||||||
|
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
|
||||||
|
|
||||||
|
log(ei.level, oss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const std::string_view activityTypeName(ActivityType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case actUnknown:
|
||||||
|
return "Unknown";
|
||||||
|
case actCopyPath:
|
||||||
|
return "CopyPath";
|
||||||
|
case actFileTransfer:
|
||||||
|
return "FileTransfer";
|
||||||
|
case actRealise:
|
||||||
|
return "Realise";
|
||||||
|
case actCopyPaths:
|
||||||
|
return "CopyPaths";
|
||||||
|
case actBuilds:
|
||||||
|
return "Builds";
|
||||||
|
case actBuild:
|
||||||
|
return "Build";
|
||||||
|
case actOptimiseStore:
|
||||||
|
return "OptimiseStore";
|
||||||
|
case actVerifyPaths:
|
||||||
|
return "VerifyPaths";
|
||||||
|
case actSubstitute:
|
||||||
|
return "Substitute";
|
||||||
|
case actQueryPathInfo:
|
||||||
|
return "QueryPathInfo";
|
||||||
|
case actPostBuildHook:
|
||||||
|
return "PostBuildHook";
|
||||||
|
case actBuildWaiting:
|
||||||
|
return "BuildWaiting";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const std::string_view resultTypeName(ResultType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case resFileLinked:
|
||||||
|
return "FileLinked";
|
||||||
|
case resBuildLogLine:
|
||||||
|
return "BuildLogLine";
|
||||||
|
case resUntrustedPath:
|
||||||
|
return "UntrustedPath";
|
||||||
|
case resCorruptedPath:
|
||||||
|
return "CorruptedPath";
|
||||||
|
case resSetPhase:
|
||||||
|
return "SetPhase";
|
||||||
|
case resProgress:
|
||||||
|
return "Progress";
|
||||||
|
case resSetExpected:
|
||||||
|
return "SetExpected";
|
||||||
|
case resPostBuildLogLine:
|
||||||
|
return "PostBuildLogLine";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string formatFields(const Logger::Fields & fields)
|
||||||
|
{
|
||||||
|
if (fields.empty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << " (";
|
||||||
|
bool first = true;
|
||||||
|
for (const auto & f : fields) {
|
||||||
|
if (!first) {
|
||||||
|
oss << ", ";
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
switch (f.type) {
|
||||||
|
case Logger::Field::tInt:
|
||||||
|
oss << f.i;
|
||||||
|
continue;
|
||||||
|
case Logger::Field::tString:
|
||||||
|
oss << f.s;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oss << ")";
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceLogger::startActivity(
|
||||||
|
ActivityId act,
|
||||||
|
Verbosity level,
|
||||||
|
ActivityType type,
|
||||||
|
const std::string & s,
|
||||||
|
const Fields & fields,
|
||||||
|
ActivityId parent
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Each activity is another track, since we have no real guarantee of what
|
||||||
|
// thread they will end on.
|
||||||
|
bool inserted = false;
|
||||||
|
{
|
||||||
|
std::lock_guard _g{impl_->lock_};
|
||||||
|
|
||||||
|
auto track = impl_->mainTrack_.child("");
|
||||||
|
inserted = impl_->activityToTrack_.insert(std::make_pair(act, track)).second;
|
||||||
|
track.begin(s + formatFields(fields), activityTypeName(type), tracing::Timestamp::now());
|
||||||
|
}
|
||||||
|
if (!inserted) {
|
||||||
|
printError("Duplicated activity ID: %d, this is a bug in Lix", act);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceLogger::stopActivity(ActivityId act)
|
||||||
|
{
|
||||||
|
std::lock_guard _g{impl_->lock_};
|
||||||
|
|
||||||
|
if (auto it = impl_->activityToTrack_.find(act); it != impl_->activityToTrack_.end()) {
|
||||||
|
it->second.end(tracing::Timestamp::now());
|
||||||
|
impl_->activityToTrack_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceLogger::result(ActivityId act, ResultType type, const Fields & fields)
|
||||||
|
{
|
||||||
|
// There are an incredibly unreasonable number of these and they are of very little value
|
||||||
|
if (type == resProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto it = impl_->activityToTrack_.find(act); it != impl_->activityToTrack_.end()) {
|
||||||
|
it->second.logMessage(
|
||||||
|
std::string(resultTypeName(type)) + formatFields(fields),
|
||||||
|
resultTypeName(type),
|
||||||
|
tracing::Timestamp::now(),
|
||||||
|
tracing::LogLevel::INFO
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
src/libutil/logging/trace-logger.hh
Normal file
36
src/libutil/logging/trace-logger.hh
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include "error.hh"
|
||||||
|
#include "logging.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/** Logger that logs to perfetto traces */
|
||||||
|
class TraceLogger : public Logger
|
||||||
|
{
|
||||||
|
struct Impl;
|
||||||
|
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TraceLogger(const nix::Path outputPath);
|
||||||
|
|
||||||
|
virtual void log(Verbosity level, std::string_view s) override;
|
||||||
|
virtual void logEI(const ErrorInfo &ei) override;
|
||||||
|
virtual void startActivity(
|
||||||
|
ActivityId act,
|
||||||
|
Verbosity level,
|
||||||
|
ActivityType type,
|
||||||
|
const std::string & s,
|
||||||
|
const Fields & fields,
|
||||||
|
ActivityId parent
|
||||||
|
) override;
|
||||||
|
virtual void stopActivity(ActivityId act) override;
|
||||||
|
virtual void result(ActivityId act, ResultType type, const Fields & fields) override;
|
||||||
|
|
||||||
|
virtual ~TraceLogger();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ libutil_sources = files(
|
||||||
'hilite.cc',
|
'hilite.cc',
|
||||||
'json-utils.cc',
|
'json-utils.cc',
|
||||||
'logging.cc',
|
'logging.cc',
|
||||||
|
'logging/chain-logger.cc',
|
||||||
|
'logging/trace-logger.cc',
|
||||||
'namespaces.cc',
|
'namespaces.cc',
|
||||||
'position.cc',
|
'position.cc',
|
||||||
'print-elided.cc',
|
'print-elided.cc',
|
||||||
|
@ -78,8 +80,10 @@ libutil_headers = files(
|
||||||
'input-accessor.hh',
|
'input-accessor.hh',
|
||||||
'json-impls.hh',
|
'json-impls.hh',
|
||||||
'json-utils.hh',
|
'json-utils.hh',
|
||||||
'logging.hh',
|
|
||||||
'logging-json.hh',
|
'logging-json.hh',
|
||||||
|
'logging.hh',
|
||||||
|
'logging/chain-logger.hh',
|
||||||
|
'logging/trace-logger.hh',
|
||||||
'lru-cache.hh',
|
'lru-cache.hh',
|
||||||
'monitor-fd.hh',
|
'monitor-fd.hh',
|
||||||
'namespaces.hh',
|
'namespaces.hh',
|
||||||
|
@ -128,6 +132,7 @@ libutil = library(
|
||||||
brotli,
|
brotli,
|
||||||
openssl,
|
openssl,
|
||||||
nlohmann_json,
|
nlohmann_json,
|
||||||
|
liblixtracing,
|
||||||
],
|
],
|
||||||
cpp_pch : cpp_pch,
|
cpp_pch : cpp_pch,
|
||||||
implicit_include_directories : true,
|
implicit_include_directories : true,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
# Subcomponents: these link into artifacts themselves, and have interdependencies.
|
# Subcomponents: these link into artifacts themselves, and have interdependencies.
|
||||||
|
|
||||||
|
# libtracing is self-contained
|
||||||
|
subdir('libtracing')
|
||||||
|
# libutil depends on libtracing
|
||||||
subdir('libutil')
|
subdir('libutil')
|
||||||
# Load-bearing order. libstore depends on libutil.
|
# Load-bearing order. libstore depends on libutil.
|
||||||
subdir('libstore')
|
subdir('libstore')
|
||||||
# libfetchers depends on libstore
|
# libfetchers depends on libstore
|
||||||
subdir('libfetchers')
|
subdir('libfetchers')
|
||||||
# libtracing is self-contained
|
|
||||||
subdir('libtracing')
|
|
||||||
# libexpr depends on all of the above
|
# libexpr depends on all of the above
|
||||||
subdir('libexpr')
|
subdir('libexpr')
|
||||||
# libmain depends on libutil and libstore
|
# libmain depends on libutil and libstore
|
||||||
|
|
Loading…
Reference in a new issue