Compare commits

...

11 Commits

6 changed files with 339 additions and 9 deletions

View File

@ -43,6 +43,7 @@
<arg choice='plain'><option>--expr</option></arg>
<arg choice='plain'><option>-E</option></arg>
</group>
<arg choice='plain'><option>--profile</option></arg>
<arg choice='plain' rep='repeat'><replaceable>files</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
@ -162,6 +163,19 @@ input.</para>
</varlistentry>
<varlistentry><term><option>--profile</option></term>
<listitem><para>When used with <option>--eval</option>, perform
evaluation in read/write mode so nix language features that
require it will still work (at the cost of needing to do
instantiation of every evaluated derivation). If this option is
not enabled, there may be uninstantiated store paths in the final
output.</para>
</listitem>
</varlistentry>
</variablelist>
<variablelist condition="manpage">

View File

@ -11,11 +11,13 @@
#include <algorithm>
#include <chrono>
#include <cstring>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <iostream>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/resource.h>
@ -349,7 +351,18 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
EvalState::~EvalState()
{
{ /*
std::cout << std::endl << "============= " << std::endl;
std::cout << "Maps summary: " << std::endl;
std::cout << "============= " << std::endl;
std::cout << "FuncMap:" << std::endl;
for(auto e: profState.funcMap) {
std::cout << e.first << ": " << e.second << std::endl;
}
std::cout << std::endl << "FileMap:" << std::endl;
for(auto e: profState.fileMap) {
std::cout << e.first << ": " << e.second << std::endl;
} */
}
@ -947,7 +960,17 @@ void ExprList::eval(EvalState & state, Env & env, Value & v)
void ExprVar::eval(EvalState & state, Env & env, Value & v)
{
Value * v2 = state.lookupVar(&env, *this, false);
state.forceValue(*v2, pos);
if(!settings.profileEvaluation) {
state.forceValue(*v2, pos);
}
else {
std::ostringstream ossName;
ossName << *this;
string name = ossName.str();
state.profState.jumpInValue(&(this->pos), name, ProfilerCallType::var);
state.forceValue(*v2, pos);
state.profState.jumpOutValue();
}
v = *v2;
}
@ -1002,8 +1025,18 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
pos2 = j->pos;
if (state.countCalls && pos2) state.attrSelects[*pos2]++;
}
state.forceValue(*vAttrs, ( pos2 != NULL ? *pos2 : this->pos ) );
if (!settings.profileEvaluation) {
state.forceValue(*vAttrs, ( pos2 != NULL ? *pos2 : this->pos ) );
}
else {
std::ostringstream ossName;
ossName << *this;
string name = ossName.str();
Pos* fpos = pos2 != NULL ? pos2 : &(this->pos);
state.profState.jumpInValue(fpos, name, ProfilerCallType::select);
state.forceValue(*vAttrs, *fpos);
state.profState.jumpOutValue();
}
} catch (Error & e) {
if (pos2 && pos2->file != state.sDerivationNix)
@ -1175,6 +1208,28 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
}
nrFunctionCalls++;
/*
Lo and behold.
There's no way to get the symbol name in the Symbol class.
That said, there's a ostream operator *WINK WINK*
*/
/* if (settings.profileEvaluation && lambda.name.set()) {
std::ostringstream ossFunc;
ossFunc << lambda.name;
string functionName = ossFunc.str();
profState.registerFunction(functionName);
if (pos.file.set()) {
std::ostringstream ossFi;
ossFi << pos.file;
string fileName = ossFi.str();
profState.registerFile(fileName);
//std::cout << "file: " << fileName << " funcName: " << functionName << " " << pos.line << ":" << pos.column << std::endl;
}
}*/
if (countCalls) incrFunctionCall(&lambda);
/* Evaluate the body. This is conditional on showTrace, because
@ -1754,6 +1809,8 @@ void EvalState::printStats()
uint64_t bLists = nrListElems * sizeof(Value *);
uint64_t bValues = nrValues * sizeof(Value);
uint64_t bAttrsets = nrAttrsets * sizeof(Bindings) + nrAttrsInAttrsets * sizeof(Attr);
if(settings.profileEvaluation)
profState.printCallGraph();
#if HAVE_BOEHMGC
GC_word heapSize, totalBytes;
@ -1975,5 +2032,178 @@ EvalSettings evalSettings;
static GlobalConfig::Register r1(&evalSettings);
/* Profiler stuff
*****************
*/
ProfilerState::ProfilerState():
profilerTreeRoot(std::nullopt),
lastProfilerCall(std::nullopt)
{
}
CompressedFileId ProfilerState::registerFile(FileName& fName) {
CompressedFileId fid;
auto it = fileMap.find(fName);
if(it == fileMap.end()) {
fid = currentFileId;
fileMap.insert({fName,currentCostCenterId});
currentFileId++;
}
else {
fid = it->second;
}
return fid;
}
CompressedCostCenterId ProfilerState::registerCostCenter(CostCenterName& fName) {
CompressedCostCenterId fid;
auto it = costMap.find(fName);
if(it == costMap.end()) {
fid = currentCostCenterId;
costMap.insert({fName,currentCostCenterId});
currentCostCenterId++;
}
else {
fid = it->second;
}
return fid;
}
void ProfilerState::jumpInValue(Pos* pos, CostCenterName name, ProfilerCallType type) {
std::shared_ptr<ProfilerCallLevel> call =
std::make_shared<ProfilerCallLevel>
(ProfilerCallLevel{
lastProfilerCall,
std::forward_list<ProfilerCallLevel*>(),
pos, name, type, false});
string delim="";
string opStr="";
std::ostringstream srcPos;
if (lastProfilerCall) {
lastProfilerCall->children.push_front(call);
} else {
std::cout << "Redifining tree root " << std::endl;
profilerTreeRoot = call;
}
callGraphStack.push(call);
lastProfilerCall = call;
// Printing stuff
int stackSize = callGraphStack.size();
for(int i=0;i<stackSize;i++) {
delim+="=";
}
switch (type) {
case ProfilerCallType::select:
opStr="select";
break;
case ProfilerCallType::var:
opStr="var";
break;
}
if(pos->file.set()) {
std::ostringstream ossFi;
ossFi << pos->file;
string fileName = ossFi.str();
srcPos << fileName << " " << pos->line << ":" << pos->column;
} else {
srcPos << "NOT DEFINED";
}
//std::cout << delim << " " << opStr << "::" << name << " " << srcPos.str() << std::endl;
}
void ProfilerState::jumpOutValue() {
if (!callGraphStack.empty())
lastProfilerCall = callGraphStack.top().get().parent.get();
else {
std::cout << "jumpOutValue, CONDITION CHELOUE" << std::endl;
}
callGraphStack.pop();
}
void ProfilerState::printCallGraph() {
std::stack<ProfilerCallLevel*> nodesToProcess;
std::ostringstream ossFi;
LineNumber line;
nodesToProcess.push(profilerTreeRoot);
while(!nodesToProcess.empty()) {
// %%%%%%%%%%%%%%%%%%%%%%%%%
// Get Node struct
// %%%%%%%%%%%%%%%%%%%%%%%%%
auto currentNode = nodesToProcess.top();
if (!currentNode->visited) {
nodesToProcess.pop();
currentNode->visited = true;
std::cout << "Current stack size: " << nodesToProcess.size() << std::endl;
string fileName;
if(currentNode->pos->file.set()) {
ossFi << currentNode->pos->file;
fileName = ossFi.str();
} else {
fileName = "NOT DEFINED";
}
FileName fname = fileName;
CostCenterName ccname = currentNode->name;
line = currentNode->pos->line;
PosCost entryPos = {line, 666};
CallGraphCostEntry costEntry = { fname, ccname, std::forward_list<CallGraphCall>(), entryPos };
auto children = currentNode->children;
// %%%%%%%%%%%%%%%%%%%%%%%%%
// Create Children Structs
// %%%%%%%%%%%%%%%%%%%%%%%%%
PosCost posCost;
CallGraphCall call;
for (auto it = children.begin(); it != children.end(); it++) {
nodesToProcess.push(*it);
if((*it)->pos->file.set()) {
ossFi << (*it)->pos->file;
fileName = ossFi.str();
} else {
fileName = "NOT DEFINED";
}
line = (*it)->pos->line;
posCost = { line, 666 };
/* fid = registerFile(fileName);
ccid = registerCostCenter(currentNode->name); */
call = { fileName, (*it)->name, posCost };
costEntry.calls.push_front(call);
}
costEntries.push_front(costEntry);
}
}
renderCostEntries(costEntries);
}
void ProfilerState::renderCostEntries(std::forward_list<CallGraphCostEntry> costEntries){
for (auto i = costEntries.begin(); i != costEntries.end(); i++) {
std::cout << "fl=" << i->fileName << std::endl;
std::cout << "fn=" << i->costCenter << std::endl;
std::cout << i->pos.line << " " << i->pos.cost << std::endl;
auto calls = i->calls;
for (auto j = calls.begin(); j != calls.end(); j++) {
std::cout << "cfi=" << j->fileName << std::endl;
std::cout << "cfn=" << j->costCenter << std::endl;
std::cout << "calls=" << "1 " << j->pos.cost << std::endl;
std::cout << "1 " << i->pos.line << std::endl;
}
std::cout << std::endl;
}
/*fl=(1) file1.c
fn=(1) main
16 20
cfn=(2) func1
calls=1 50
16 400
cfi=(2) file2.c
cfn=(3) func2
calls=3 20
16 400*/
}
}

View File

@ -8,7 +8,11 @@
#include "config.hh"
#include "function-trace.hh"
#include <forward_list>
#include <map>
#include <optional>
#include <optional>
#include <stack>
#include <unordered_map>
@ -19,6 +23,74 @@ class Store;
class EvalState;
enum RepairFlag : bool;
/* Profiler-related operations.
[callGrindSpec]: TODO: add URL */
typedef string CostCenterName;
typedef string FileName;
typedef int CompressedCostCenterId;
typedef int CompressedFileId;
typedef int LineNumber;
typedef int BullshitCost;
struct PosCost {
LineNumber line;
BullshitCost cost;
};
struct CallCost {
LineNumber line;
int nbCalls;
};
struct CallGraphCall {
FileName fileName;
CostCenterName costCenter;
PosCost pos;
};
struct CallGraphCostEntry {
FileName fileName;
CostCenterName costCenter;
std::forward_list<CallGraphCall> calls;
PosCost pos;
};
enum ProfilerCallType { select=0, var};
struct ProfilerCallLevel {
std::shared_ptr<ProfilerCallLevel> parent;
std::forward_list<std::shared_ptr<ProfilerCallLevel>> children;
Pos* pos;
CostCenterName name;
ProfilerCallType type;
bool visited;
};
class ProfilerState {
public:
ProfilerState();
CompressedFileId registerFile(FileName& fName);
CompressedCostCenterId registerCostCenter(CostCenterName& fName);
void jumpInValue(Pos* pos, CostCenterName name, ProfilerCallType type);
void jumpOutValue();
void printCallGraph();
private:
/* We index every func and file to leverage Callgrind's string compression.
See section "3.1.6.Subposition Compression" section from [callgrindSpec]. */
std::map<CostCenterName,CompressedCostCenterId> costMap;
std::map<FileName,CompressedFileId> fileMap;
CompressedCostCenterId currentCostCenterId;
CompressedFileId currentFileId;
std::stack<std::shared_ptr<ProfilerCallLevel>> callGraphStack;
std::optional<std::shared_ptr<ProfilerCallLevel>> lastProfilerCall;
std::optional<std::shared_ptr<ProfilerCallLevel>> profilerTreeRoot;
std::forward_list<CallGraphCostEntry> costEntries;
void renderCostEntries(std::forward_list<CallGraphCostEntry> costEntries);
};
typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v);
@ -311,6 +383,10 @@ private:
friend struct ExprOpConcatLists;
friend struct ExprSelect;
friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
/* Profiler-related members */
ProfilerState profState;
};
@ -353,6 +429,7 @@ struct EvalSettings : Config
Setting<bool> traceFunctionCalls{this, false, "trace-function-calls",
"Emit log messages for each function entry and exit at the 'vomit' log level (-vvvv)"};
};
extern EvalSettings evalSettings;

View File

@ -54,6 +54,11 @@ public:
}
friend std::ostream & operator << (std::ostream & str, const Symbol & sym);
string str() const
{
return *s;
}
};
class SymbolTable

View File

@ -353,8 +353,10 @@ public:
Setting<Paths> pluginFiles{this, {}, "plugin-files",
"Plugins to dynamically load at nix initialization time."};
};
Setting<bool> profileEvaluation{this, false, "profile-function-calls",
"Generates a callgraph profile summary which you can interpret using kcachegrind."};
};
// FIXME: don't use a global variable.
extern Settings settings;

View File

@ -140,6 +140,8 @@ static int _main(int argc, char * * argv)
repair = Repair;
else if (*arg == "--dry-run")
settings.readOnlyMode = true;
else if (*arg == "--profile")
settings.profileEvaluation = true;
else if (*arg != "" && arg->at(0) == '-')
return false;
else