mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-20 11:30:18 +00:00
add line_profiler
impl
This commit is contained in:
parent
8a2ee65301
commit
f2ce3c97d7
@ -1,9 +1,38 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "frame.h"
|
||||||
|
|
||||||
namespace pkpy {
|
namespace pkpy {
|
||||||
|
|
||||||
struct LineProfiler{
|
struct LineRecord{
|
||||||
|
int line;
|
||||||
|
SourceData* src;
|
||||||
|
|
||||||
|
i64 hits;
|
||||||
|
clock_t time;
|
||||||
|
|
||||||
|
clock_t time_per_hit() const {
|
||||||
|
return time / hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineRecord(int line, SourceData* src): line(line), src(src), hits(0), time(0) {}
|
||||||
|
|
||||||
|
std::string_view line_content() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LineProfiler{
|
||||||
|
clock_t prev_time;
|
||||||
|
LineRecord* prev_record;
|
||||||
|
int prev_line;
|
||||||
|
|
||||||
|
// filename -> records
|
||||||
|
std::map<std::string_view, std::map<int, LineRecord>> records;
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void _step(Frame* frame);
|
||||||
|
void _step_end();
|
||||||
|
void end();
|
||||||
|
Str stats();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pkpy
|
} // namespace pkpy
|
||||||
|
@ -67,7 +67,8 @@ PyObject* VM::_run_top_frame(){
|
|||||||
{
|
{
|
||||||
|
|
||||||
#define CEVAL_STEP_CALLBACK() \
|
#define CEVAL_STEP_CALLBACK() \
|
||||||
if(_ceval_on_step) _ceval_on_step(this, frame.get(), byte);
|
if(_ceval_on_step) _ceval_on_step(this, frame.get(), byte); \
|
||||||
|
if(_profiler) _profiler->_step(frame.get());
|
||||||
|
|
||||||
#define DISPATCH_OP_CALL() { frame = top_frame(); goto __NEXT_FRAME; }
|
#define DISPATCH_OP_CALL() { frame = top_frame(); goto __NEXT_FRAME; }
|
||||||
__NEXT_FRAME:
|
__NEXT_FRAME:
|
||||||
|
@ -254,10 +254,12 @@ struct LineProfilerW{
|
|||||||
vm->ValueError("only one profiler can be enabled at a time");
|
vm->ValueError("only one profiler can be enabled at a time");
|
||||||
}
|
}
|
||||||
vm->_profiler = &profiler;
|
vm->_profiler = &profiler;
|
||||||
|
profiler.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void disable_by_count(VM* vm){
|
void disable_by_count(VM* vm){
|
||||||
vm->_profiler = nullptr;
|
vm->_profiler = nullptr;
|
||||||
|
profiler.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
static void _register(VM* vm, PyObject* mod, PyObject* type){
|
||||||
@ -273,15 +275,17 @@ struct LineProfilerW{
|
|||||||
self.enable_by_count(vm);
|
self.enable_by_count(vm);
|
||||||
PyObject* func = view[1];
|
PyObject* func = view[1];
|
||||||
const Tuple& args = CAST(Tuple&, view[2]);
|
const Tuple& args = CAST(Tuple&, view[2]);
|
||||||
for(PyObject* arg : args) vm->s_data.push(arg);
|
|
||||||
vm->s_data.push(func);
|
vm->s_data.push(func);
|
||||||
|
vm->s_data.push(PY_NULL);
|
||||||
|
for(PyObject* arg : args) vm->s_data.push(arg);
|
||||||
PyObject* ret = vm->vectorcall(args.size());
|
PyObject* ret = vm->vectorcall(args.size());
|
||||||
self.disable_by_count(vm);
|
self.disable_by_count(vm);
|
||||||
return ret;
|
return ret;
|
||||||
});
|
});
|
||||||
|
|
||||||
vm->bind(type, "print_stats(self)", [](VM* vm, ArgsView args){
|
vm->bind(type, "print_stats(self)", [](VM* vm, ArgsView args){
|
||||||
// ...
|
LineProfilerW& self = PK_OBJ_GET(LineProfilerW, args[0]);
|
||||||
|
vm->stdout_write(self.profiler.stats());
|
||||||
return vm->None;
|
return vm->None;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,84 @@
|
|||||||
|
|
||||||
namespace pkpy{
|
namespace pkpy{
|
||||||
|
|
||||||
|
static std::string left_pad(std::string s, int width){
|
||||||
|
int n = width - s.size();
|
||||||
|
if(n <= 0) return s;
|
||||||
|
return std::string(n, ' ') + s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string to_string_1f(double x){
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, 32, "%.1f", x);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view LineRecord::line_content() const {
|
||||||
|
auto [_0, _1] = src->_get_line(line);
|
||||||
|
return std::string_view(_0, _1-_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineProfiler::begin(){
|
||||||
|
prev_time = 0;
|
||||||
|
prev_record = nullptr;
|
||||||
|
prev_line = -1;
|
||||||
|
records.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineProfiler::_step(Frame *frame){
|
||||||
|
std::string_view filename = frame->co->src->filename.sv();
|
||||||
|
int line = frame->co->lines[frame->_ip];
|
||||||
|
// std::string_view function = frame->co->name.sv();
|
||||||
|
|
||||||
|
if(prev_record == nullptr){
|
||||||
|
prev_time = clock();
|
||||||
|
}else{
|
||||||
|
_step_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int, LineRecord>& file_records = records[filename];
|
||||||
|
auto [it, ok] = file_records.insert({line, LineRecord(line, frame->co->src.get())});
|
||||||
|
prev_record = &(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineProfiler::_step_end(){
|
||||||
|
clock_t now = clock();
|
||||||
|
clock_t delta = now - prev_time;
|
||||||
|
prev_time = now;
|
||||||
|
if(prev_record->line != prev_line){
|
||||||
|
prev_record->hits++;
|
||||||
|
prev_line = prev_record->line;
|
||||||
|
}
|
||||||
|
prev_record->time += delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineProfiler::end(){
|
||||||
|
_step_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
Str LineProfiler::stats(){
|
||||||
|
SStream ss;
|
||||||
|
for(auto& [filename, file_records] : records){
|
||||||
|
clock_t total_time = 0;
|
||||||
|
for(auto& [line, record] : file_records){
|
||||||
|
total_time += record.time;
|
||||||
|
}
|
||||||
|
ss << "Total time: " << (double)total_time / CLOCKS_PER_SEC << "s\n";
|
||||||
|
ss << "File: " << filename << "\n";
|
||||||
|
// ss << "Function: " << "<?>" << "at line " << -1 << "\n";
|
||||||
|
ss << "Line # Hits Time Per Hit % Time Line Contents\n";
|
||||||
|
ss << "==============================================================\n";
|
||||||
|
for(auto& [line, record]: file_records){
|
||||||
|
ss << left_pad(std::to_string(line), 6);
|
||||||
|
ss << left_pad(std::to_string(record.hits), 10);
|
||||||
|
ss << left_pad(std::to_string(record.time), 13);
|
||||||
|
ss << left_pad(std::to_string(record.time_per_hit()), 9);
|
||||||
|
ss << left_pad(to_string_1f(record.time * 100.0 / total_time), 9);
|
||||||
|
ss << " " << record.line_content() << "\n";
|
||||||
|
}
|
||||||
|
ss << "\n";
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pkpy
|
} // namespace pkpy
|
15
tests/84_line_profiler.py
Normal file
15
tests/84_line_profiler.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from line_profiler import LineProfiler
|
||||||
|
|
||||||
|
def my_func():
|
||||||
|
a = 0
|
||||||
|
for i in range(1000000):
|
||||||
|
a += i
|
||||||
|
return a
|
||||||
|
|
||||||
|
lp = LineProfiler()
|
||||||
|
|
||||||
|
lp.add_function(my_func)
|
||||||
|
|
||||||
|
lp.runcall(my_func)
|
||||||
|
|
||||||
|
lp.print_stats()
|
Loading…
x
Reference in New Issue
Block a user