Download
128 lines 3.3 KiBCModified 2019-03-05 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
/*
* lsampleprof.c
* A Lua sample profiling library by Bruce Hill. This library returns a single
* function of the form profile([backprop=.619, [rate=100]], fn, ...)
* that runs `fn` with any extra args, performs sample profiling, and
* returns a table of the results.
*
*    - `rate` the average number of samples per millisecond
*    - `backprop` controls how weights are propagated up the callstack. 0 means "only tally
*       the current line", 1 means "tally the current line and everything above it in the
*       callstack equally", 0.5 means "tally each line with half weight of the thing below
*       it in the callstack"
*  
*  The results table is a mapping from "file:line" -> number of times a random sample landed on that line
*/

#include "lua.h"
#include "lauxlib.h"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <math.h>

#define DEFAULT_BACKPROP 0.619
#define DEFAULT_RATE 100

static lua_State *lastL;
static lua_Number backprop = DEFAULT_BACKPROP;
static lua_Number rate = DEFAULT_RATE;

static inline void randomalarm()
{
    // Exponential decay:
    int r = rand();
    int delay = -log((double)r/(double)RAND_MAX) * 1e3 / rate;
    ualarm(1 + delay, 0);
}

static inline void bump(lua_State *L, lua_Debug *ar, lua_Number amount)
{
    int profile_index = lua_gettop(L);
    lua_getinfo(L, "lS", ar);
    lua_pushfstring(L, "%s:%d", ar->short_src, ar->currentline);
    int key_index = lua_gettop(L);
    lua_pushvalue(L, key_index);
    int type = lua_gettable(L, profile_index);
    if (type == LUA_TNIL) {
        lua_pop(L, 1);
        lua_pushnumber(L, amount);
    } else {
        lua_Number count = lua_tonumber(L, -1);
        count += amount;
        lua_pop(L, 1);
        lua_pushnumber(L, count);
    }
    lua_settable(L, profile_index);
}

static void ltakesample(lua_State *L, lua_Debug *ar)
{
    lua_pushlightuserdata(L, &lastL);
    lua_gettable(L, LUA_REGISTRYINDEX);

    lua_Number weight = 1.0;
    bump(L, ar, weight);
    lua_Debug ar2;
    for (int i = 0; lua_getstack(L, i, &ar2); i++) {
        weight *= backprop;
        bump(L, &ar2, weight);
    }
    // Disable hook and set alarm
    lua_sethook(lastL, NULL, 0, 0);
    randomalarm();
}

static void handler(int sig)
{
    (void)sig;
    lua_sethook(lastL, ltakesample, LUA_MASKLINE, 0);
}

static int Lwrap(lua_State *L)
{
    // Backprop
    if (lua_type(L, 1) == LUA_TNUMBER) {
        backprop = lua_tonumber(L, 1);
        lua_remove(L, 1);
    }
    // Sampling rate
    if (lua_type(L, 1) == LUA_TNUMBER) {
        rate = lua_tonumber(L, 1);
        lua_remove(L, 1);
    }

    struct sigaction newaction, oldaction;
    memset(&newaction, sizeof(newaction), 0);
    newaction.sa_handler = handler;
    newaction.sa_flags = SA_NODEFER;
    sigaction(SIGALRM, &newaction, &oldaction);
    randomalarm();

    lastL = L;
    lua_call(L, lua_gettop(L)-1, 0);
    lua_sethook(L, NULL, 0, 0);

    alarm(0);
    //sigaction(SIGALRM, &oldaction, NULL);

    backprop = DEFAULT_BACKPROP;
    rate = DEFAULT_RATE;

    lua_pushlightuserdata(L, &lastL);
    lua_gettable(L, LUA_REGISTRYINDEX);

    return 1;
}

LUALIB_API int luaopen_sampleprof(lua_State *L)
{
    lua_pushlightuserdata(L, &lastL);
    lua_createtable(L, 0, 128);
    lua_settable(L, LUA_REGISTRYINDEX);
    lua_pushcfunction(L, Lwrap);
    return 1;
}