2016-02-21 21:12:13 +01:00
|
|
|
//
|
|
|
|
// This module allows performance monitoring by looking at
|
|
|
|
// the PC at regular intervals and building a histogram
|
|
|
|
//
|
|
|
|
// perf.start(start, end, nbins[, pc offset on stack])
|
|
|
|
// perf.stop() -> total sample, samples outside range, table { addr -> count , .. }
|
|
|
|
|
|
|
|
|
|
|
|
#include "ets_sys.h"
|
|
|
|
#include "os_type.h"
|
|
|
|
#include "osapi.h"
|
|
|
|
#include "c_stdlib.h"
|
|
|
|
|
|
|
|
#include "module.h"
|
|
|
|
#include "lauxlib.h"
|
|
|
|
#include "platform.h"
|
|
|
|
#include "hw_timer.h"
|
|
|
|
#include "cpu_esp8266.h"
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
int ref;
|
|
|
|
uint32_t start;
|
|
|
|
uint32_t bucket_shift;
|
|
|
|
uint32_t bucket_count;
|
|
|
|
uint32_t total_samples;
|
|
|
|
uint32_t outside_samples;
|
|
|
|
uint32_t pc_offset;
|
|
|
|
uint32_t bucket[1];
|
|
|
|
} DATA;
|
|
|
|
|
|
|
|
static DATA *data;
|
|
|
|
extern char _flash_used_end[];
|
|
|
|
|
|
|
|
#define TIMER_OWNER ((os_param_t) 'p')
|
|
|
|
|
|
|
|
static void ICACHE_RAM_ATTR hw_timer_cb(os_param_t p)
|
|
|
|
{
|
|
|
|
(void) p;
|
|
|
|
uint32_t stackaddr;
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
uint32_t pc = *(&stackaddr + data->pc_offset);
|
|
|
|
|
|
|
|
uint32_t bucket_number = (pc - data->start) >> data->bucket_shift;
|
|
|
|
if (bucket_number < data->bucket_count) {
|
|
|
|
data->bucket[bucket_number]++;
|
|
|
|
} else {
|
|
|
|
data->outside_samples++;
|
|
|
|
}
|
|
|
|
data->total_samples++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int perf_start(lua_State *L)
|
|
|
|
{
|
|
|
|
uint32_t start = luaL_optinteger(L, 1, 0x40000000);
|
|
|
|
uint32_t end = luaL_optinteger(L, 2, (uint32_t) _flash_used_end);
|
|
|
|
uint32_t bins = luaL_optinteger(L, 3, 1024);
|
|
|
|
|
|
|
|
if (end <= start) {
|
|
|
|
luaL_error(L, "end must be larger than start");
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t binsize = (end - start + bins - 1) / bins;
|
|
|
|
|
|
|
|
// Round up to a power of two
|
|
|
|
int shift;
|
|
|
|
binsize = binsize - 1;
|
|
|
|
for (shift = 0; binsize > 0; shift++) {
|
|
|
|
binsize >>= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bins = (end - start + (1 << shift) - 1) / (1 << shift);
|
|
|
|
|
|
|
|
int pc_offset = 20; // This appears to be correct
|
|
|
|
if (lua_gettop(L) >= 4) {
|
|
|
|
pc_offset = luaL_checkinteger(L, 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t data_size = sizeof(DATA) + bins * sizeof(uint32_t);
|
|
|
|
DATA *d = (DATA *) lua_newuserdata(L, data_size);
|
|
|
|
memset(d, 0, data_size);
|
|
|
|
d->ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
|
|
d->start = start;
|
|
|
|
d->bucket_shift = shift;
|
|
|
|
d->bucket_count = bins;
|
|
|
|
d->pc_offset = pc_offset;
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
lua_unref(L, data->ref);
|
|
|
|
}
|
|
|
|
|
|
|
|
data = d;
|
|
|
|
|
|
|
|
// Start the timer
|
2016-12-01 22:14:20 +01:00
|
|
|
if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, TRUE)) {
|
2016-02-21 21:12:13 +01:00
|
|
|
// Failed to init the timer
|
|
|
|
data = NULL;
|
|
|
|
lua_unref(L, d->ref);
|
|
|
|
luaL_error(L, "Unable to initialize timer");
|
|
|
|
}
|
|
|
|
|
|
|
|
platform_hw_timer_set_func(TIMER_OWNER, hw_timer_cb, 0);
|
|
|
|
platform_hw_timer_arm_us(TIMER_OWNER, 50);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int perf_stop(lua_State *L)
|
|
|
|
{
|
|
|
|
if (!data) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// stop the timer
|
|
|
|
platform_hw_timer_close(TIMER_OWNER);
|
|
|
|
|
|
|
|
DATA *d = data;
|
|
|
|
data = NULL;
|
|
|
|
|
|
|
|
lua_pushnumber(L, d->total_samples);
|
|
|
|
lua_pushnumber(L, d->outside_samples);
|
|
|
|
lua_newtable(L);
|
|
|
|
int i;
|
|
|
|
uint32_t addr = d->start;
|
|
|
|
for (i = 0; i < d->bucket_count; i++, addr += (1 << d->bucket_shift)) {
|
|
|
|
if (d->bucket[i]) {
|
|
|
|
lua_pushnumber(L, addr);
|
|
|
|
lua_pushnumber(L, d->bucket[i]);
|
|
|
|
lua_settable(L, -3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_pushnumber(L, 1 << d->bucket_shift);
|
|
|
|
|
|
|
|
lua_unref(L, d->ref);
|
|
|
|
|
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const LUA_REG_TYPE perf_map[] = {
|
|
|
|
{ LSTRKEY( "start" ), LFUNCVAL( perf_start ) },
|
|
|
|
{ LSTRKEY( "stop" ), LFUNCVAL( perf_stop ) },
|
|
|
|
{ LNILKEY, LNILVAL }
|
|
|
|
};
|
|
|
|
|
|
|
|
NODEMCU_MODULE(PERF, "perf", perf_map, NULL);
|