mirror of
https://github.com/plasma-umass/coz.git
synced 2024-09-11 04:55:25 +03:00
Callchain and inlining support
This commit is contained in:
parent
184306ff4d
commit
bba7864925
@ -6,6 +6,9 @@ TEST_ARGS =
|
||||
|
||||
include $(ROOT)/common.mk
|
||||
|
||||
CFLAGS += -O3
|
||||
CXXFLAGS += -O3
|
||||
|
||||
bench:: debug/bin/producer_consumer
|
||||
$(ROOT)/release/bin/causal --- debug/bin/producer_consumer $(ARGS)
|
||||
|
||||
|
@ -4,10 +4,12 @@
|
||||
#include <pthread.h>
|
||||
#include <cassert>
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include <causal.h>
|
||||
|
||||
enum {
|
||||
Items = 100000,
|
||||
Items = 1000000,
|
||||
QueueSize = 10,
|
||||
ProducerCount = 5,
|
||||
ConsumerCount = 3
|
||||
@ -15,8 +17,7 @@ enum {
|
||||
|
||||
int produced = 0;
|
||||
int consumed = 0;
|
||||
int queue_size;
|
||||
int queue[QueueSize];
|
||||
std::queue<int> queue;
|
||||
|
||||
pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_cond_t producer_condvar = PTHREAD_COND_INITIALIZER;
|
||||
@ -26,11 +27,10 @@ pthread_cond_t main_condvar = PTHREAD_COND_INITIALIZER;
|
||||
void* producer(void* arg) {
|
||||
for(size_t n = 0; n < Items / ProducerCount; n++) {
|
||||
pthread_mutex_lock(&queue_lock);
|
||||
while(queue_size == QueueSize) {
|
||||
while(queue.size() == QueueSize) {
|
||||
pthread_cond_wait(&producer_condvar, &queue_lock);
|
||||
}
|
||||
queue[queue_size] = 123;
|
||||
queue_size++;
|
||||
queue.push(123);
|
||||
produced++;
|
||||
pthread_mutex_unlock(&queue_lock);
|
||||
pthread_cond_signal(&consumer_condvar);
|
||||
@ -44,16 +44,16 @@ void* producer(void* arg) {
|
||||
void* consumer(void* arg) {
|
||||
while(true) {
|
||||
pthread_mutex_lock(&queue_lock);
|
||||
while(queue_size == 0) {
|
||||
while(queue.size() == 0) {
|
||||
pthread_cond_wait(&consumer_condvar, &queue_lock);
|
||||
}
|
||||
queue_size--;
|
||||
assert(queue[queue_size] == 123);
|
||||
queue[queue_size] = 321;
|
||||
int front = queue.front();
|
||||
queue.pop();
|
||||
assert(front == 123);
|
||||
consumed++;
|
||||
pthread_mutex_unlock(&queue_lock);
|
||||
pthread_cond_signal(&producer_condvar);
|
||||
//CAUSAL_PROGRESS;
|
||||
CAUSAL_PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ CFLAGS =
|
||||
|
||||
include $(ROOT)/common.mk
|
||||
|
||||
debug:: $(ROOT)/debug/lib/libcausal_support.a
|
||||
debug/lib/libcausal.so:: $(ROOT)/debug/lib/libcausal_support.a
|
||||
|
||||
release:: $(ROOT)/release/lib/libcausal_support.a
|
||||
release/lib/libcausal.so:: $(ROOT)/release/lib/libcausal_support.a
|
||||
|
||||
CXXFLAGS += --std=c++11 -fPIC
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
namespace causal {
|
||||
@ -13,11 +13,10 @@ namespace causal {
|
||||
boost::program_options::options_description desc("Causal options");
|
||||
desc.add_options()
|
||||
("help,h", "show this help message")
|
||||
("exclude-main,e", "do not profile the main executable")
|
||||
("include,i",
|
||||
boost::program_options::value<std::vector<std::string>>()
|
||||
->default_value(std::vector<std::string>(), ""),
|
||||
"profile libraries with matching names")
|
||||
"evaluate optimizations to files in the specified file/directory")
|
||||
("fixed-line,l",
|
||||
boost::program_options::value<std::string>()->default_value(""),
|
||||
"profile with a fixed source line as the optimization candidate (<file>:<line>)")
|
||||
|
@ -27,10 +27,10 @@ enum {
|
||||
|
||||
class profiler {
|
||||
public:
|
||||
void include_file(const std::string& filename, uintptr_t load_address);
|
||||
void register_counter(Counter* c);
|
||||
void startup(const std::string& output_filename,
|
||||
const std::vector<std::string>& source_progress_names,
|
||||
std::vector<std::string> scope,
|
||||
const std::string& fixed_line_name,
|
||||
int fixed_speedup);
|
||||
void shutdown();
|
||||
@ -70,6 +70,9 @@ private:
|
||||
/// Process all available samples and insert delays.
|
||||
void process_samples(thread_state::ref& state);
|
||||
|
||||
/// Find the source line that contains a given sample, walking the callchain if necessary
|
||||
std::shared_ptr<causal_support::line> find_containing_line(perf_event::record& sample);
|
||||
|
||||
/// Just insert delays
|
||||
void add_delays(thread_state::ref& state);
|
||||
|
||||
|
@ -56,45 +56,13 @@ int wrapped_main(int argc, char** argv, char** env) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the specified file patterns
|
||||
vector<string> file_patterns = args["include"].as<vector<string>>();
|
||||
|
||||
// If the main executable should NOT be excluded, add the program name to the file patterns set
|
||||
if(!args.count("exclude-main")) {
|
||||
file_patterns.push_back(argv[causal_argc + 1]);
|
||||
}
|
||||
|
||||
// Walk through all the loaded executable images
|
||||
for(const auto& file : causal_support::get_loaded_files()) {
|
||||
const string& filename = file.first;
|
||||
uintptr_t load_address = file.second;
|
||||
|
||||
// Exclude libcausal
|
||||
if(filename.find("libcausal") == string::npos) {
|
||||
// Check if the loaded file matches any of the specified patterns
|
||||
bool matched = false;
|
||||
for(const string& pat : file_patterns) {
|
||||
if(filename.find(pat) != string::npos) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(matched) {
|
||||
INFO << "Processing file " << filename;
|
||||
profiler::get_instance().include_file(filename, load_address);
|
||||
} else {
|
||||
WARNING << "Omitting file " << filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all the real function pointers for interposed functions
|
||||
real::init();
|
||||
|
||||
// Start the profiler
|
||||
profiler::get_instance().startup(args["output"].as<string>(),
|
||||
args["progress"].as<vector<string>>(),
|
||||
args["include"].as<vector<string>>(),
|
||||
args["fixed-line"].as<string>(),
|
||||
args["fixed-speedup"].as<int>());
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <asm/unistd.h>
|
||||
#include <execinfo.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
|
||||
@ -32,14 +33,9 @@ using namespace std;
|
||||
void on_error(int, siginfo_t*, void*);
|
||||
void samples_ready(int, siginfo_t*, void*);
|
||||
|
||||
void profiler::include_file(const string& filename, uintptr_t load_address) {
|
||||
PREFER(_map.process_file(filename, load_address))
|
||||
<< "Failed to locate debug version of " << filename;
|
||||
}
|
||||
|
||||
void profiler::register_counter(Counter* c) {
|
||||
_out->add_counter(c);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up the profiling environment and start the main profiler thread
|
||||
@ -47,6 +43,7 @@ void profiler::register_counter(Counter* c) {
|
||||
*/
|
||||
void profiler::startup(const string& output_filename,
|
||||
const vector<string>& source_progress_names,
|
||||
vector<string> scope,
|
||||
const string& fixed_line_name,
|
||||
int fixed_speedup) {
|
||||
|
||||
@ -65,6 +62,20 @@ void profiler::startup(const string& output_filename,
|
||||
real::sigaction(SIGSEGV, &sa, nullptr);
|
||||
real::sigaction(SIGABRT, &sa, nullptr);
|
||||
|
||||
// If the file scope is empty, add the current working directory
|
||||
if(scope.size() == 0) {
|
||||
char cwd[PATH_MAX];
|
||||
getcwd(cwd, PATH_MAX);
|
||||
scope.push_back(string(cwd));
|
||||
}
|
||||
|
||||
// Build the address -> source map
|
||||
_map.build(scope);
|
||||
|
||||
for(const auto& f : _map.files()) {
|
||||
INFO << "Including source file " << f.first;
|
||||
}
|
||||
|
||||
// If a non-empty fixed line was provided, attempt to locate it
|
||||
if(fixed_line_name != "") {
|
||||
_fixed_line = _map.find_line(fixed_line_name);
|
||||
@ -256,6 +267,27 @@ void profiler::end_sampling() {
|
||||
state->sampler.close();
|
||||
}
|
||||
|
||||
shared_ptr<line> profiler::find_containing_line(perf_event::record& sample) {
|
||||
if(!sample.is_sample())
|
||||
return shared_ptr<line>();
|
||||
|
||||
// Check if the sample occurred in known code
|
||||
shared_ptr<line> l = _map.find_line(sample.get_ip());
|
||||
if(l)
|
||||
return l;
|
||||
|
||||
// Walk the callchain
|
||||
for(uint64_t pc : sample.get_callchain()) {
|
||||
l = _map.find_line(pc);
|
||||
if(l) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
// No hits. Return null
|
||||
return shared_ptr<line>();
|
||||
}
|
||||
|
||||
void profiler::process_samples(thread_state::ref& state) {
|
||||
// Stop sampling
|
||||
state->sampler.stop();
|
||||
@ -263,7 +295,7 @@ void profiler::process_samples(thread_state::ref& state) {
|
||||
for(perf_event::record r : state->sampler) {
|
||||
if(r.is_sample()) {
|
||||
// Find the line that contains this sample
|
||||
shared_ptr<line> l = _map.find_line(r.get_ip());
|
||||
shared_ptr<line> l = find_containing_line(r);
|
||||
|
||||
// Load the selected line
|
||||
line* current_line = _selected_line.load();
|
||||
|
@ -8,6 +8,12 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace dwarf {
|
||||
class die;
|
||||
class line_table;
|
||||
}
|
||||
|
||||
namespace causal_support {
|
||||
class file;
|
||||
@ -120,11 +126,8 @@ namespace causal_support {
|
||||
inline const std::map<std::string, std::shared_ptr<file>>& files() const { return _files; }
|
||||
inline const std::map<interval, std::shared_ptr<line>>& ranges() const { return _ranges; }
|
||||
|
||||
/**
|
||||
* Locate a debug version of the specified file and add its line number information
|
||||
* to the memory map.
|
||||
*/
|
||||
bool process_file(const std::string& name, uintptr_t load_address = 0);
|
||||
/// Build a map from addresses to source lines with the provided source file scope
|
||||
void build(const std::vector<std::string>& scope);
|
||||
|
||||
std::shared_ptr<line> find_line(const std::string& name);
|
||||
std::shared_ptr<line> find_line(uintptr_t addr);
|
||||
@ -141,15 +144,22 @@ namespace causal_support {
|
||||
}
|
||||
}
|
||||
|
||||
void add_range(std::string filename, size_t line_no, interval range);
|
||||
|
||||
/// Find a debug version of provided file and add all of its in-scope lines to the map
|
||||
bool process_file(const std::string& name, uintptr_t load_address,
|
||||
const std::vector<std::string>& scope);
|
||||
|
||||
/// Add entries for all inlined calls
|
||||
void process_inlines(const dwarf::die& d,
|
||||
const dwarf::line_table& table,
|
||||
const std::vector<std::string>& scope,
|
||||
uintptr_t load_address);
|
||||
|
||||
std::map<std::string, std::shared_ptr<file>> _files;
|
||||
std::map<interval, std::shared_ptr<line>> _ranges;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a map of all loaded executables and libraries in the current process
|
||||
*/
|
||||
std::map<std::string, uintptr_t> get_loaded_files();
|
||||
|
||||
static std::ostream& operator<<(std::ostream& os, const interval& i) {
|
||||
os << std::hex << "0x" << i.get_base() << "-0x" << i.get_limit() << std::dec;
|
||||
return os;
|
||||
|
@ -29,6 +29,7 @@
|
||||
using boost::is_any_of;
|
||||
using boost::split;
|
||||
|
||||
using boost::filesystem::absolute;
|
||||
using boost::filesystem::canonical;
|
||||
using boost::filesystem::exists;
|
||||
using boost::filesystem::path;
|
||||
@ -182,7 +183,7 @@ namespace causal_support {
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
map<string, uintptr_t> get_loaded_files() {
|
||||
map<string, uintptr_t> result;
|
||||
|
||||
@ -202,8 +203,135 @@ namespace causal_support {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void memory_map::build(const vector<string>& scope) {
|
||||
for(const auto& f : get_loaded_files()) {
|
||||
INFO << "Processing " << f.first;
|
||||
if(!process_file(f.first, f.second, scope)) {
|
||||
INFO << " Couldn't locate debug version of " << f.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool in_scope(string file, const vector<string>& scope) {
|
||||
for(const string& s : scope) {
|
||||
if(path(file).normalize().string().find(s) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
dwarf::value find_attribute(const dwarf::die& d, dwarf::DW_AT attr) {
|
||||
if(!d.valid())
|
||||
return dwarf::value();
|
||||
|
||||
if(d.has(attr))
|
||||
return d[attr];
|
||||
|
||||
if(d.has(dwarf::DW_AT::abstract_origin)) {
|
||||
const dwarf::die child = d.resolve(dwarf::DW_AT::abstract_origin).as_reference();
|
||||
dwarf::value v = find_attribute(child, attr);
|
||||
if(v.valid())
|
||||
return v;
|
||||
}
|
||||
|
||||
if(d.has(dwarf::DW_AT::specification)) {
|
||||
const dwarf::die child = d.resolve(dwarf::DW_AT::specification).as_reference();
|
||||
dwarf::value v = find_attribute(child, attr);
|
||||
if(v.valid())
|
||||
return v;
|
||||
}
|
||||
|
||||
return dwarf::value();
|
||||
}
|
||||
|
||||
void memory_map::add_range(std::string filename, size_t line_no, interval range) {
|
||||
shared_ptr<file> f = get_file(filename);
|
||||
shared_ptr<line> l = f->get_line(line_no);
|
||||
|
||||
auto iter = _ranges.find(range);
|
||||
if(iter != _ranges.end() && iter->second != l) {
|
||||
WARNING << "Overlapping entries for lines "
|
||||
<< f->get_name() << ":" << l->get_line() << " and "
|
||||
<< iter->second->get_file()->get_name() << ":" << iter->second->get_line();
|
||||
}
|
||||
|
||||
bool memory_map::process_file(const string& name, uintptr_t load_address) {
|
||||
// Add the entry
|
||||
_ranges.emplace(range, l);
|
||||
}
|
||||
|
||||
void memory_map::process_inlines(const dwarf::die& d,
|
||||
const dwarf::line_table& table,
|
||||
const vector<string>& scope,
|
||||
uintptr_t load_address) {
|
||||
|
||||
if(!d.valid())
|
||||
return;
|
||||
|
||||
if(d.tag == dwarf::DW_TAG::inlined_subroutine) {
|
||||
string name;
|
||||
dwarf::value name_val = find_attribute(d, dwarf::DW_AT::name);
|
||||
if(name_val.valid()) {
|
||||
name = name_val.as_string();
|
||||
}
|
||||
|
||||
string decl_file;
|
||||
dwarf::value decl_file_val = find_attribute(d, dwarf::DW_AT::decl_file);
|
||||
if(decl_file_val.valid()) {
|
||||
decl_file = table.get_file(decl_file_val.as_uconstant())->path;
|
||||
}
|
||||
|
||||
size_t decl_line = 0;
|
||||
dwarf::value decl_line_val = find_attribute(d, dwarf::DW_AT::decl_line);
|
||||
if(decl_line_val.valid())
|
||||
decl_line = decl_line_val.as_uconstant();
|
||||
|
||||
string call_file;
|
||||
if(d.has(dwarf::DW_AT::call_file)) {
|
||||
call_file = table.get_file(d[dwarf::DW_AT::call_file].as_uconstant())->path;
|
||||
}
|
||||
|
||||
size_t call_line = 0;
|
||||
if(d.has(dwarf::DW_AT::call_line)) {
|
||||
call_line = d[dwarf::DW_AT::call_line].as_uconstant();
|
||||
}
|
||||
|
||||
// If the call location is in scope but the function is not, add an entry
|
||||
if(decl_file.size() > 0 && call_file.size() > 0) {
|
||||
if(!in_scope(decl_file, scope) && in_scope(call_file, scope)) {
|
||||
// Does this inline have separate ranges?
|
||||
dwarf::value ranges_val = find_attribute(d, dwarf::DW_AT::ranges);
|
||||
if(ranges_val.valid()) {
|
||||
// Add each range
|
||||
for(auto r : ranges_val.as_rangelist()) {
|
||||
add_range(call_file,
|
||||
call_line,
|
||||
interval(r.low, r.high) + load_address);
|
||||
}
|
||||
} else {
|
||||
// Must just be one range. Add it
|
||||
dwarf::value low_pc_val = find_attribute(d, dwarf::DW_AT::low_pc);
|
||||
dwarf::value high_pc_val = find_attribute(d, dwarf::DW_AT::high_pc);
|
||||
|
||||
if(low_pc_val.valid() && high_pc_val.valid()) {
|
||||
add_range(call_file,
|
||||
call_line,
|
||||
interval(low_pc_val.as_address(),
|
||||
high_pc_val.as_address()) + load_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& child : d) {
|
||||
process_inlines(child, table, scope, load_address);
|
||||
}
|
||||
}
|
||||
|
||||
bool memory_map::process_file(const string& name, uintptr_t load_address,
|
||||
const vector<string>& scope) {
|
||||
elf::elf f = locate_debug_executable(name);
|
||||
|
||||
// If a debug version of the file could not be located, return false
|
||||
@ -222,22 +350,10 @@ namespace causal_support {
|
||||
// Walk through the line instructions in the DWARF line table
|
||||
for(auto& line_info : unit.get_line_table()) {
|
||||
// Insert an entry if this isn't the first line command in the sequence
|
||||
if(prev_address != 0) {
|
||||
// Get or create the file handle for this entry
|
||||
shared_ptr<file> f = get_file(prev_filename);
|
||||
// Get or create the line handle for this entry
|
||||
shared_ptr<line> l = f->get_line(prev_line);
|
||||
// Make the memory range that holds this line
|
||||
interval range = interval(prev_address, line_info.address) + load_address;
|
||||
|
||||
auto iter = _ranges.find(range);
|
||||
if(iter != _ranges.end() && iter->second != l) {
|
||||
WARNING << "Overlapping entries for lines " << f->get_name() << ":" << l->get_line()
|
||||
<< " and " << iter->second->get_file()->get_name() << ":" << iter->second->get_line();
|
||||
}
|
||||
|
||||
// Add the entry
|
||||
_ranges.emplace(range, l);
|
||||
if(prev_address != 0 && in_scope(prev_filename, scope)) {
|
||||
add_range(prev_filename,
|
||||
prev_line,
|
||||
interval(prev_address, line_info.address) + load_address);
|
||||
}
|
||||
|
||||
if(line_info.end_sequence) {
|
||||
@ -248,6 +364,8 @@ namespace causal_support {
|
||||
prev_address = line_info.address;
|
||||
}
|
||||
}
|
||||
|
||||
process_inlines(unit.root(), unit.get_line_table(), scope, load_address);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -9,5 +9,9 @@ include $(ROOT)/common.mk
|
||||
|
||||
CXXFLAGS += --std=c++11 -fPIC
|
||||
|
||||
debug:: $(ROOT)/lib/support/debug/lib/libcausal_support.a
|
||||
|
||||
release:: $(ROOT)/lib/support/release/lib/libcausal_support.a
|
||||
|
||||
test: debug
|
||||
./inspect $(ROOT)/tests/kmeans/kmeans
|
||||
./debug/bin/inspect $(ROOT)/benchmarks/producer_consumer/debug/bin/producer_consumer
|
||||
|
@ -1,10 +1,15 @@
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "support.h"
|
||||
#include "log.h"
|
||||
|
||||
using std::cerr;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if(argc != 2) {
|
||||
@ -13,15 +18,27 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
causal_support::memory_map m;
|
||||
REQUIRE(m.process_file(argv[1])) << "Couldn't find a debug version of " << argv[1];
|
||||
|
||||
vector<string> scope;
|
||||
// If the file scope is empty, add the current working directory
|
||||
char cwd[PATH_MAX];
|
||||
getcwd(cwd, PATH_MAX);
|
||||
scope.push_back(string(cwd));
|
||||
|
||||
m.build(scope);
|
||||
|
||||
size_t file_count = 0;
|
||||
size_t line_count = 0;
|
||||
for(const auto& f_info : m.files()) {
|
||||
const string& filename = f_info.first;
|
||||
file_count++;
|
||||
for(const auto& l_info : f_info.second->lines()) {
|
||||
line_count++;
|
||||
size_t line_number = l_info.first;
|
||||
INFO << l_info.second;
|
||||
}
|
||||
}
|
||||
|
||||
INFO << "Found " << line_count << " lines in " << file_count << " files.";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user