mirror of
https://github.com/fluencelabs/redis
synced 2025-03-23 02:50:50 +00:00
Lua debugger: output improvements, eval command.
This commit is contained in:
parent
1f8d614423
commit
cf4700bda4
@ -526,11 +526,9 @@ sds sdsCatColorizedLdbReply(sds o, char *s, size_t len) {
|
||||
if (strstr(s,"<reply>")) color = "cyan";
|
||||
if (strstr(s,"<error>")) color = "red";
|
||||
if (strstr(s,"<value>")) color = "magenta";
|
||||
if (isdigit(s[0])) {
|
||||
char *p = s+1;
|
||||
while(isdigit(*p)) p++;
|
||||
if (*p == '*') color = "yellow"; /* Current line. */
|
||||
else if (*p == '#') color = "bold"; /* Break point. */
|
||||
if (len > 4 && isdigit(s[3])) {
|
||||
if (s[1] == '>') color = "yellow"; /* Current line. */
|
||||
else if (s[2] == '#') color = "bold"; /* Break point. */
|
||||
}
|
||||
return sdscatcolor(o,s,len,color);
|
||||
}
|
||||
@ -1030,6 +1028,28 @@ static int issueCommand(int argc, char **argv) {
|
||||
return issueCommandRepeat(argc, argv, config.repeat);
|
||||
}
|
||||
|
||||
/* Split the user provided command into multiple SDS arguments.
|
||||
* This function normally uses sdssplitargs() from sds.c which is able
|
||||
* to understand "quoted strings", escapes and so forth. However when
|
||||
* we are in Lua debugging mode and the "eval" command is used, we want
|
||||
* the remaining Lua script (after "e " or "eval ") to be passed verbatim
|
||||
* as a single big argument. */
|
||||
static sds *cliSplitArgs(char *line, int *argc) {
|
||||
if (config.eval_ldb && (strstr(line,"eval ") == line ||
|
||||
strstr(line,"e ") == line))
|
||||
{
|
||||
sds *argv = zmalloc(sizeof(sds)*2);
|
||||
*argc = 2;
|
||||
int len = strlen(line);
|
||||
int elen = line[1] == ' ' ? 2 : 5; /* "e " or "eval "? */
|
||||
argv[0] = sdsnewlen(line,elen-1);
|
||||
argv[1] = sdsnewlen(line+elen,len-elen);
|
||||
return argv;
|
||||
} else {
|
||||
return sdssplitargs(line,argc);
|
||||
}
|
||||
}
|
||||
|
||||
static void repl(void) {
|
||||
sds historyfile = NULL;
|
||||
int history = 0;
|
||||
@ -1053,7 +1073,7 @@ static void repl(void) {
|
||||
cliRefreshPrompt();
|
||||
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
argv = sdssplitargs(line,&argc);
|
||||
argv = cliSplitArgs(line,&argc);
|
||||
if (history) linenoiseHistoryAdd(line);
|
||||
if (historyfile) linenoiseHistorySave(historyfile);
|
||||
|
||||
|
208
src/scripting.c
208
src/scripting.c
@ -51,9 +51,11 @@ void ldbEnable(client *c);
|
||||
void evalGenericCommandWithDebugging(client *c, int evalsha);
|
||||
void luaLdbLineHook(lua_State *lua, lua_Debug *ar);
|
||||
void ldbLog(sds entry);
|
||||
void ldbLogRedisReply(char *reply, size_t maxlen);
|
||||
|
||||
/* Debugger shared state is stored inside this global structure. */
|
||||
#define LDB_BREAKPOINTS_MAX 64
|
||||
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
|
||||
#define LDB_REPLY_MAX_LOG_LEN 60 /* Max chars when logging a reply. */
|
||||
struct ldbState {
|
||||
int fd; /* Socket of the debugging client. */
|
||||
int active; /* Are we debugging EVAL right now? */
|
||||
@ -107,13 +109,11 @@ void sha1hex(char *digest, char *script, size_t len) {
|
||||
* Basically we take the arguments, execute the Redis command in the context
|
||||
* of a non connected client, then take the generated reply and convert it
|
||||
* into a suitable Lua type. With this trick the scripting feature does not
|
||||
* need the introduction of a full Redis internals API. Basically the script
|
||||
* need the introduction of a full Redis internals API. The script
|
||||
* is like a normal client that bypasses all the slow I/O paths.
|
||||
*
|
||||
* Note: in this function we do not do any sanity check as the reply is
|
||||
* generated by Redis directly. This allows us to go faster.
|
||||
* The reply string can be altered during the parsing as it is discarded
|
||||
* after the conversion is completed.
|
||||
*
|
||||
* Errors are returned as a table with a single 'err' field set to the
|
||||
* error string.
|
||||
@ -123,21 +123,11 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
||||
char *p = reply;
|
||||
|
||||
switch(*p) {
|
||||
case ':':
|
||||
p = redisProtocolToLuaType_Int(lua,reply);
|
||||
break;
|
||||
case '$':
|
||||
p = redisProtocolToLuaType_Bulk(lua,reply);
|
||||
break;
|
||||
case '+':
|
||||
p = redisProtocolToLuaType_Status(lua,reply);
|
||||
break;
|
||||
case '-':
|
||||
p = redisProtocolToLuaType_Error(lua,reply);
|
||||
break;
|
||||
case '*':
|
||||
p = redisProtocolToLuaType_MultiBulk(lua,reply);
|
||||
break;
|
||||
case ':': p = redisProtocolToLuaType_Int(lua,reply); break;
|
||||
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
|
||||
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
|
||||
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
|
||||
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply); break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@ -579,12 +569,8 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
redisProtocolToLuaType(lua,reply);
|
||||
|
||||
/* If the debugger is active, log the reply from Redis. */
|
||||
if (ldb.active && ldb.step) {
|
||||
sds replycopy = sdsnew("<reply> ");
|
||||
replycopy = sdscat(replycopy,reply); /* It's always null terminated. */
|
||||
if (sdslen(replycopy) > 70) sdsrange(replycopy,0,69);
|
||||
ldbLog(replycopy);
|
||||
}
|
||||
if (ldb.active && ldb.step)
|
||||
ldbLogRedisReply(reply,LDB_REPLY_MAX_LOG_LEN);
|
||||
|
||||
/* Sort the output array if needed, assuming it is a non-null multi bulk
|
||||
* reply as expected. */
|
||||
@ -1687,6 +1673,25 @@ protoerr:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Log the specified line in the Lua debugger output. */
|
||||
void ldbLogSourceLine(int lnum) {
|
||||
char *line = ldbGetSourceLine(lnum);
|
||||
char *prefix;
|
||||
int bp = ldbIsBreakpoint(lnum);
|
||||
int current = ldb.currentline == lnum;
|
||||
|
||||
if (current && bp)
|
||||
prefix = "->#";
|
||||
else if (current)
|
||||
prefix = "-> ";
|
||||
else if (bp)
|
||||
prefix = " #";
|
||||
else
|
||||
prefix = " ";
|
||||
sds thisline = sdscatprintf(sdsempty(),"%s%-3d %s", prefix, lnum, line);
|
||||
ldbLog(thisline);
|
||||
}
|
||||
|
||||
/* Implement the "list" command of the Lua debugger. If around is 0
|
||||
* the whole file is listed, otherwise only a small portion of the file
|
||||
* around the specified line is shown. When a line number is specified
|
||||
@ -1697,22 +1702,16 @@ void ldbList(int around, int context) {
|
||||
|
||||
for (j = 1; j <= ldb.lines; j++) {
|
||||
if (around != 0 && abs(around-j) > context) continue;
|
||||
char *line = ldbGetSourceLine(j);
|
||||
int mark;
|
||||
if (ldb.currentline == j)
|
||||
mark = '*';
|
||||
else
|
||||
mark = ldbIsBreakpoint(j) ? '#' : ':';
|
||||
sds thisline = sdscatprintf(sdsempty(),"%d%c %s", j, mark, line);
|
||||
ldbLog(thisline);
|
||||
ldbLogSourceLine(j);
|
||||
}
|
||||
}
|
||||
|
||||
/* Produce a debugger log entry representing the value of the Lua object
|
||||
* currently on the top of the stack. */
|
||||
void ldbLogStackValue(lua_State *lua) {
|
||||
* currently on the top of the stack. As a side effect the element is
|
||||
* popped. */
|
||||
void ldbLogStackValue(lua_State *lua, char *prefix) {
|
||||
int t = lua_type(lua,-1);
|
||||
sds s = sdsnew("<value> ");
|
||||
sds s = sdsnew(prefix);
|
||||
|
||||
switch(t) {
|
||||
case LUA_TSTRING:
|
||||
@ -1751,6 +1750,91 @@ void ldbLogStackValue(lua_State *lua) {
|
||||
lua_pop(lua,1);
|
||||
}
|
||||
|
||||
char *ldbRedisProtocolToHuman_Int(sds *o, char *reply);
|
||||
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply);
|
||||
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply);
|
||||
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply);
|
||||
|
||||
/* Get Redis protocol from 'reply' and appends it in human readable form to
|
||||
* the passed SDS string 'o'.
|
||||
*
|
||||
* Note that the SDS string is passed by reference (pointer of pointer to
|
||||
* char*) so that we can return a modified pointer, as for SDS semantics. */
|
||||
char *ldbRedisProtocolToHuman(sds *o, char *reply) {
|
||||
char *p = reply;
|
||||
switch(*p) {
|
||||
case ':': p = ldbRedisProtocolToHuman_Int(o,reply); break;
|
||||
case '$': p = ldbRedisProtocolToHuman_Bulk(o,reply); break;
|
||||
case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break;
|
||||
case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break;
|
||||
case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
char *ldbRedisProtocolToHuman_Int(sds *o, char *reply) {
|
||||
char *p = strchr(reply+1,'\r');
|
||||
*o = sdscatlen(*o,reply+1,p-reply-1);
|
||||
return p+2;
|
||||
}
|
||||
|
||||
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply) {
|
||||
char *p = strchr(reply+1,'\r');
|
||||
long long bulklen;
|
||||
|
||||
string2ll(reply+1,p-reply-1,&bulklen);
|
||||
if (bulklen == -1) {
|
||||
*o = sdscatlen(*o,"NULL",4);
|
||||
return p+2;
|
||||
} else {
|
||||
*o = sdscatrepr(*o,p+2,bulklen);
|
||||
return p+2+bulklen+2;
|
||||
}
|
||||
}
|
||||
|
||||
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply) {
|
||||
char *p = strchr(reply+1,'\r');
|
||||
|
||||
*o = sdscatrepr(*o,reply,p-reply);
|
||||
return p+2;
|
||||
}
|
||||
|
||||
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) {
|
||||
char *p = strchr(reply+1,'\r');
|
||||
long long mbulklen;
|
||||
int j = 0;
|
||||
|
||||
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||
p += 2;
|
||||
if (mbulklen == -1) {
|
||||
*o = sdscatlen(*o,"NULL",4);
|
||||
return p;
|
||||
}
|
||||
*o = sdscatlen(*o,"[",1);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
p = ldbRedisProtocolToHuman(o,p);
|
||||
if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
|
||||
}
|
||||
*o = sdscatlen(*o,"]",1);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Log a Redis reply as debugger output, in an human readable format.
|
||||
* If the resulting string is longer than 'len' plus a few more chars
|
||||
* used as prefix, it gets truncated. */
|
||||
void ldbLogRedisReply(char *reply, size_t maxlen) {
|
||||
sds log = sdsnew("<reply> ");
|
||||
maxlen += sdslen(log);
|
||||
ldbRedisProtocolToHuman(&log,reply);
|
||||
/* Trip and add ... if the length was reached, to hint the user it's not
|
||||
* the whole reply. */
|
||||
if (sdslen(log) > maxlen) {
|
||||
sdsrange(log,0,maxlen-1);
|
||||
log = sdscatlen(log," ...",4);
|
||||
}
|
||||
ldbLog(log);
|
||||
}
|
||||
|
||||
/* Implements the "print" command of the Lua debugger. It scans for Lua
|
||||
* var "varname" starting from the current stack frame up to the top stack
|
||||
* frame. The first matching variable is printed. */
|
||||
@ -1765,7 +1849,7 @@ void ldbPrint(lua_State *lua, char *varname) {
|
||||
while((name = lua_getlocal(lua,&ar,i)) != NULL) {
|
||||
i++;
|
||||
if (strcmp(varname,name) == 0) {
|
||||
ldbLogStackValue(lua);
|
||||
ldbLogStackValue(lua,"<value> ");
|
||||
return;
|
||||
} else {
|
||||
lua_pop(lua,1); /* Discard the var name on the stack. */
|
||||
@ -1784,10 +1868,8 @@ void ldbBreak(sds *argv, int argc) {
|
||||
} else {
|
||||
ldbLog(sdscatfmt(sdsempty(),"%i breakpoints set:",ldb.bpcount));
|
||||
int j;
|
||||
for (j = 0; j < ldb.bpcount; j++) {
|
||||
ldbLog(sdscatfmt(sdsempty(),"%i# %s", ldb.bp[j],
|
||||
ldbGetSourceLine(ldb.bp[j])));
|
||||
}
|
||||
for (j = 0; j < ldb.bpcount; j++)
|
||||
ldbLogSourceLine(ldb.bp[j]);
|
||||
}
|
||||
} else {
|
||||
int j;
|
||||
@ -1819,6 +1901,28 @@ void ldbBreak(sds *argv, int argc) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Implements the Lua debugger "eval" command. It just compiles the user
|
||||
* passed fragment of code and executes it, showing the result left on
|
||||
* the stack. */
|
||||
void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||||
/* Glue the script together if it is composed of multiple arguments. */
|
||||
sds code = sdsjoinsds(argv+1,argc-1," ",1);
|
||||
|
||||
if (luaL_loadbuffer(lua,code,sdslen(code),"@ldb_eval")) {
|
||||
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
|
||||
lua_pop(lua,1);
|
||||
sdsfree(code);
|
||||
return;
|
||||
}
|
||||
sdsfree(code);
|
||||
if (lua_pcall(lua,0,1,0)) {
|
||||
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
|
||||
lua_pop(lua,1);
|
||||
return;
|
||||
}
|
||||
ldbLogStackValue(lua,"<retval> ");
|
||||
}
|
||||
|
||||
/* Read debugging commands from client. */
|
||||
void ldbRepl(lua_State *lua) {
|
||||
sds *argv;
|
||||
@ -1845,8 +1949,9 @@ void ldbRepl(lua_State *lua) {
|
||||
ldb.cbuf = sdsempty();
|
||||
|
||||
/* Execute the command. */
|
||||
if (!strcasecmp(argv[0],"help")) {
|
||||
if (!strcasecmp(argv[0],"h") || !strcasecmp(argv[0],"help")) {
|
||||
ldbLog(sdsnew("Redis Lua debugger help:"));
|
||||
ldbLog(sdsnew("[h]elp Show this help."));
|
||||
ldbLog(sdsnew("[s]tep Run current line and stop again."));
|
||||
ldbLog(sdsnew("[n]ext Alias for step."));
|
||||
ldbLog(sdsnew("[c]continue Run till next breakpoint."));
|
||||
@ -1857,6 +1962,7 @@ ldbLog(sdsnew("[b]eark Show all breakpoints."));
|
||||
ldbLog(sdsnew("[b]eark <line> Add a breakpoint to the specified line."));
|
||||
ldbLog(sdsnew("[b]eark -<line> Remove breakpoint from the specified line."));
|
||||
ldbLog(sdsnew("[b]eark 0 Remove all breakpoints."));
|
||||
ldbLog(sdsnew("[e]eval <code> Execute some Lua code in a new callframe."));
|
||||
ldbSendLogs();
|
||||
} else if (!strcasecmp(argv[0],"s") || !strcasecmp(argv[0],"step") ||
|
||||
!strcasecmp(argv[0],"n") || !strcasecmp(argv[0],"next")) {
|
||||
@ -1864,9 +1970,12 @@ ldbLog(sdsnew("[b]eark 0 Remove all breakpoints."));
|
||||
break;
|
||||
} else if (!strcasecmp(argv[0],"c") || !strcasecmp(argv[0],"continue")){
|
||||
break;
|
||||
} else if (!strcasecmp(argv[0],"b") || !strcasecmp(argv[0],"break")){
|
||||
} else if (!strcasecmp(argv[0],"b") || !strcasecmp(argv[0],"break")) {
|
||||
ldbBreak(argv,argc);
|
||||
ldbSendLogs();
|
||||
} else if (!strcasecmp(argv[0],"e") || !strcasecmp(argv[0],"eval")) {
|
||||
ldbEval(lua,argv,argc);
|
||||
ldbSendLogs();
|
||||
} else if (argc == 2 &&
|
||||
(!strcasecmp(argv[0],"p") || !strcasecmp(argv[0],"print")))
|
||||
{
|
||||
@ -1879,7 +1988,7 @@ ldbLog(sdsnew("[b]eark 0 Remove all breakpoints."));
|
||||
ldbList(around,ctx);
|
||||
ldbSendLogs();
|
||||
} else {
|
||||
ldbLog(sdsnew("Unknown Redis Lua debugger command or "
|
||||
ldbLog(sdsnew("<error> Unknown Redis Lua debugger command or "
|
||||
"wrong number of arguments."));
|
||||
ldbSendLogs();
|
||||
}
|
||||
@ -1899,12 +2008,15 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||
lua_getinfo(lua,"Sl",ar);
|
||||
if(strstr(ar->short_src,"user_script") == NULL) return;
|
||||
|
||||
if (ldb.step || ldbIsBreakpoint(ar->currentline)) {
|
||||
int bp = ldbIsBreakpoint(ar->currentline);
|
||||
if (ldb.step || bp) {
|
||||
char *reason = bp ? "break point" : "step over";
|
||||
ldb.currentline = ar->currentline;
|
||||
ldb.step = 0;
|
||||
int mark = ldbIsBreakpoint(ldb.currentline) ? '#' : '*';
|
||||
ldbLog(sdscatprintf(sdsempty(),"%d%c %s", (int)ar->currentline,
|
||||
mark, ldbGetSourceLine(ar->currentline)));
|
||||
ldbLog(sdscatprintf(sdsempty(),
|
||||
"* Stopped at %d, stop reason = %s",
|
||||
ldb.currentline, reason));
|
||||
ldbLogSourceLine(ldb.currentline);
|
||||
ldbSendLogs();
|
||||
ldbRepl(lua);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user