Exemple #1
0
/** Writes a message to the log file.
    @param type The type of message to log
    @param fileName The filename where this function was called from.
    @param lineNum The line number in the file where this function was called from.
    @param funcName The name of the function which called this function.
    @param msg The message to log
*/
void msg_details(msg_type type, const char *fileName, int lineNum, const char *funcName, const char *msg, ...)
{
	msg_init();
	
	/* Construct a string for the user's message */
	char msgbuf[1024];
	va_list args;
	va_start(args, msg);
	vsnprintf(msgbuf, 1024, msg, args);
	va_end(args);

	/* Remove any newlines at the end of the message. */
	int msgbufidx = strlen(msgbuf)-1;
	while(msgbuf[msgbufidx] == '\n')
	{
		msgbuf[msgbufidx] = '\0';
		msgbufidx--;
	}
	
	/* info to prepend to message printed to console */
	char typestr[1024];
	msg_type_string(type, typestr, 1024);

	/* Determine the stream that we are going to print out to: stdout,
	 * stderr, or don't print to console */
	FILE *stream = stdout;
	if(type == ERROR || type == FATAL)
		stream = stderr;
	if(msg_show_type(type) == 0)
		stream = NULL;

	char timestamp[1024];
	msg_timestamp(timestamp, 1024);
	char *fileNameCopy = strdup(fileName);
	char *shortFileName = basename(fileNameCopy);
	
	/* Print the message to stderr or stdout */
	if(stream)
	{
		msg_start_color(type, stream);
		/* Print additional details to console for fatal errors */
		if(type == FATAL)
		{
			fprintf(stream, "%s %s\n", typestr, msgbuf);
			fprintf(stream, "%s Occurred at %s:%d in the function %s()\n",
			        typestr, shortFileName, lineNum, funcName);
		}
		else
			fprintf(stream, "%s %s\n", typestr, msgbuf);
		msg_end_color(type, stream);
	}

	// Not using funcName to try to keep log shorter.
	fprintf(f, "%s%s %12s:%-4d %s\n", typestr, timestamp, shortFileName, lineNum, msgbuf);
	free(fileNameCopy);

	/* Ensure messages are written to the file or console. */
	fflush(stream);
	fflush(f);
}
Exemple #2
0
static void
req_log(struct msg *req)
{
    struct msg *rsp;           /* peer message (response) */
    int64_t req_time;          /* time cost for this request */
    char *peer_str;            /* peer client ip:port */
    uint32_t req_len, rsp_len; /* request and response length */
    struct string *req_type;   /* request type string */

    if (log_loggable(LOG_NOTICE) == 0) {
        return;
    }

    /* a fragment? */
    if (req->frag_id != 0 && req->frag_owner != req) {
        return;
    }

    /* conn close normally? */
    if (req->mlen == 0) {
        return;
    }

    req_time = nc_usec_now() - req->start_ts;

    rsp = req->peer;
    req_len = req->mlen;
    rsp_len = (rsp != NULL) ? rsp->mlen : 0;

    if (req->key_end) {
        req->key_end[0] = '\0';
    }

    /*
     * FIXME: add backend addr here
     * Maybe we can store addrstr just like server_pool in conn struct
     * when connections are resolved
     */
    peer_str = nc_unresolve_peer_desc(req->owner->sd);

    req_type = msg_type_string(req->type);

    log_debug(LOG_NOTICE, "req %"PRIu64" done on c %d req_time %"PRIi64".%03"PRIi64
              " msec type %.*s narg %"PRIu32" req_len %"PRIu32" rsp_len %"PRIu32
              " key0 '%s' peer '%s' done %d error %d",
              req->id, req->owner->sd, req_time / 1000, req_time % 1000,
              req_type->len, req_type->data, req->narg, req_len, rsp_len,
              req->key_start, peer_str, req->done, req->error);
}
Exemple #3
0
static void
req_log(struct msg *req)
{
    struct msg *rsp;           /* peer message (response) */
    int64_t req_time;          /* time cost for this request */
    char *peer_str;            /* peer client ip:port */
    uint32_t req_len, rsp_len; /* request and response length */
    struct string *req_type;   /* request type string */
    struct keypos *kpos;

    if (log_loggable(LOG_NOTICE) == 0) {
        return;
    }

    /* a fake request? */
    if (req->owner == NULL) {
        return;
    }

    /* a fragment? */
    if (req->frag_id != 0 && req->frag_owner != req) {
        return;
    }

    /* conn close normally? */
    if (req->mlen == 0) {
        return;
    }
    /*
     * there is a race scenario where a requests comes in, the log level is not LOG_NOTICE,
     * and before the response arrives you modify the log level to LOG_NOTICE
     * using SIGTTIN OR SIGTTOU, then req_log() wouldn't have msg->start_ts set
     */
    if (req->start_ts == 0) {
        return;
    }

    req_time = nc_usec_now() - req->start_ts;

    rsp = req->peer;
    req_len = req->mlen;
    rsp_len = (rsp != NULL) ? rsp->mlen : 0;

    if (array_n(req->keys) < 1) {
        return;
    }

    kpos = array_get(req->keys, 0);
    if (kpos->end != NULL) {
        *(kpos->end) = '\0';
    }

    /*
     * FIXME: add backend addr here
     * Maybe we can store addrstr just like server_pool in conn struct
     * when connections are resolved
     */
    peer_str = nc_unresolve_peer_desc(req->owner->sd);

    req_type = msg_type_string(req->type);

    log_debug(LOG_NOTICE, "req %"PRIu64" done on c %d req_time %"PRIi64".%03"PRIi64
              " msec type %.*s narg %"PRIu32" req_len %"PRIu32" rsp_len %"PRIu32
              " key0 '%s' peer '%s' done %d error %d",
              req->id, req->owner->sd, req_time / 1000, req_time % 1000,
              req_type->len, req_type->data, req->narg, req_len, rsp_len,
              kpos->start, peer_str, req->done, req->error);
}
Exemple #4
0
rstatus_t
slowlog_command_make_reply(struct context *ctx, 
	struct conn *conn, struct msg *msg, struct msg *pmsg)
{
	rstatus_t status;
	uint32_t nkeys;
	struct keypos *kp;
	char *contents;
    uint32_t subcmdlen;
	
	ASSERT(conn->client && !conn->proxy);
    ASSERT(msg->request);
	ASSERT(pmsg != NULL && !pmsg->request);
    ASSERT(msg->owner == conn);
	ASSERT(conn->owner == ctx->manager);

	nkeys = array_n(msg->keys);
	ASSERT(nkeys >= 1);

	kp = array_get(msg->keys, 0);
    subcmdlen = (uint32_t)(kp->end-kp->start);
    if (subcmdlen==strlen("reset")&&!memcmp(kp->start,"reset",subcmdlen)){
        if (nkeys != 1) {
            goto format_error;
        }
        
        slowlog_reset();
        status = msg_append_full(pmsg, (uint8_t*)"OK", 2);
        if (status != NC_OK) {
    		conn->err = ENOMEM;
            return status;
        }
        
        goto done;
    } else if (subcmdlen==strlen("id")&&!memcmp(kp->start,"id",subcmdlen)){
        int buf_len;
        uint8_t buf[30];
        long long id;

        if (nkeys != 1) {
            goto format_error;
        }

        pthread_rwlock_rdlock(&rwlocker);
        id = slowlog_entry_id;
        pthread_rwlock_unlock(&rwlocker);
        
        buf_len = nc_scnprintf(buf,30,"%lld",id);
        status = msg_append_full(pmsg, buf, (size_t)buf_len);
        if (status != NC_OK) {
    		conn->err = ENOMEM;
            return status;
        }
        goto done;
    } else if (subcmdlen==strlen("len")&&!memcmp(kp->start,"len",subcmdlen)){
        int len, buf_len;
        uint8_t buf[20];

        if (nkeys != 1) {
            goto format_error;
        }

        pthread_rwlock_rdlock(&rwlocker);
        len = slowlog_len;
        pthread_rwlock_unlock(&rwlocker);
        
        buf_len = nc_scnprintf(buf,20,"%d",len);
        status = msg_append_full(pmsg, buf, (size_t)buf_len);
        if (status != NC_OK) {
    		conn->err = ENOMEM;
            return status;
        }
        goto done;
    } else if (subcmdlen==strlen("get")&&!memcmp(kp->start,"get",subcmdlen)){
        int count, sent = 0, buf_len;
        uint8_t buf[50];
        slowlog_entry *se;
        struct string *str;
        
        if (nkeys == 1) {
            count = 10;
        } else if (nkeys == 2) {
            kp = array_get(msg->keys, 1);
            count = nc_atoi(kp->start, (kp->end-kp->start));
            if (count < 0) {
                goto format_error;
            }
        } else {
            goto format_error;
        }

        pthread_rwlock_rdlock(&rwlocker);
        se = STAILQ_FIRST(&slowlog);
        while(count-- && se != NULL) {
            int nfield;
            uint32_t j;            

            sent++;
            buf_len = nc_scnprintf(buf,50,"%d) 1) %lld\r\n",sent, se->id);
            status = msg_append_full(pmsg, buf, (size_t)buf_len);
            if (status != NC_OK) {
                pthread_rwlock_unlock(&rwlocker);
        		conn->err = ENOMEM;
                return status;
            }
            buf_len = nc_scnprintf(buf,50,"    2) %lld\r\n",se->time);
            status = msg_append_full(pmsg, buf, (size_t)buf_len);
            if (status != NC_OK) {
                pthread_rwlock_unlock(&rwlocker);
        		conn->err = ENOMEM;
                return status;
            }
            buf_len = nc_scnprintf(buf,50,"    3) %lld\r\n",se->duration);
            status = msg_append_full(pmsg, buf, (size_t)buf_len);
            if (status != NC_OK) {
                pthread_rwlock_unlock(&rwlocker);
        		conn->err = ENOMEM;
                return status;
            }
            str = msg_type_string(se->cmdtype);
            nfield = 1;
            buf_len = nc_scnprintf(buf,50,"    4) %d) %s\r\n",nfield++,str->data);
            status = msg_append_full(pmsg, buf, (size_t)buf_len);
            if (status != NC_OK) {
                pthread_rwlock_unlock(&rwlocker);
        		conn->err = ENOMEM;
                return status;
            }
            buf_len = nc_scnprintf(buf,50,"       %d) %d\r\n",nfield++,se->keys_count);
            status = msg_append_full(pmsg, buf, (size_t)buf_len);
            if (status != NC_OK) {
                pthread_rwlock_unlock(&rwlocker);
        		conn->err = ENOMEM;
                return status;
            }
            if (se->keys != NULL) {
                for (j = 0; j < array_n(se->keys); j ++) {
                    str = array_get(se->keys, j);
                    
                    buf_len = nc_scnprintf(buf,50,"       %d) ",nfield++);
                    status = msg_append_full(pmsg, buf, (size_t)buf_len);
                    if (status != NC_OK) {
                        pthread_rwlock_unlock(&rwlocker);
                		conn->err = ENOMEM;
                        return status;
                    }
                    status = msg_append_full(pmsg, str->data, (size_t)str->len);
                    if (status != NC_OK) {
                        pthread_rwlock_unlock(&rwlocker);
                		conn->err = ENOMEM;
                        return status;
                    }
                    status = msg_append_full(pmsg, (uint8_t *)CRLF, CRLF_LEN);
                    if (status != NC_OK) {
                        pthread_rwlock_unlock(&rwlocker);
                		conn->err = ENOMEM;
                        return status;
                    }
                }
            }
            se = STAILQ_NEXT(se, next);
        }
        pthread_rwlock_unlock(&rwlocker);
        
        if (msg_empty(pmsg)) {
            status = msg_append_full(pmsg, (uint8_t*)"END", 3);
            if (status != NC_OK) {
        		conn->err = ENOMEM;
                return status;
            }
            goto done;
        }
        return NC_OK;
    } else if (subcmdlen==strlen("overview") &&
        !memcmp(kp->start,"overview",subcmdlen)) {
        int count, buf_len;
        uint8_t buf[50];
        int j, idx, id;
        struct statistics_oneday *so;
        
        if (nkeys == 1) {
            count = 10;
        } else if (nkeys == 2) {
            kp = array_get(msg->keys, 1);
            count = nc_atoi(kp->start, (kp->end-kp->start));
            if (count < 0) {
                goto format_error;
            }
        } else {
            goto format_error;
        }

        if (slowlog_statistics == NULL) {
            status = msg_append_full(pmsg, (uint8_t*)"END", 3);
            if (status != NC_OK) {
        		conn->err = ENOMEM;
                return status;
            }
            goto done;
        }

        if (count > statistics_days)
            count = statistics_days;
        pthread_mutex_lock(&statistics_locker);
        idx = today_idx;
        id = 1;
        while (count--) {
            so = &slowlog_statistics[idx];

            if (so->year == 0)
                break;

            buf_len = nc_scnprintf(buf,50,"%d) %d-%d-%d ",id++,so->year+1900,so->mon+1,so->day);
            status = msg_append_full(pmsg, buf, (size_t)buf_len);
            if (status != NC_OK) {
                pthread_mutex_unlock(&statistics_locker);
        		conn->err = ENOMEM;
                return status;
            }

            for (j = 0; j < statistics_period-1; j ++) {
                buf_len = nc_scnprintf(buf,50,"%lld ",so->periods[j]);
                status = msg_append_full(pmsg, buf, (size_t)buf_len);
                if (status != NC_OK) {
                    pthread_mutex_unlock(&statistics_locker);
            		conn->err = ENOMEM;
                    return status;
                }
            }
            buf_len = nc_scnprintf(buf,50,"%lld\r\n",so->periods[statistics_period-1]);
            status = msg_append_full(pmsg, buf, (size_t)buf_len);
            if (status != NC_OK) {
                pthread_mutex_unlock(&statistics_locker);
        		conn->err = ENOMEM;
                return status;
            }
            
            if (--idx < 0) {
                idx = statistics_days - 1;
            }
        }
        pthread_mutex_unlock(&statistics_locker);

        if (msg_empty(pmsg)) {
            status = msg_append_full(pmsg, (uint8_t*)"END", 3);
            if (status != NC_OK) {
        		conn->err = ENOMEM;
                return status;
            }
            goto done;
        }
        return NC_OK;
    } else {
        goto format_error;
    }

format_error:
    contents = "ERR: slowlog command format is error.";
    status = msg_append_full(pmsg, (uint8_t *)contents, strlen(contents));
    if (status != NC_OK) {
		conn->err = ENOMEM;
        return status;
    }
    
    goto done;
done:
    status = msg_append_full(pmsg, (uint8_t *)CRLF, CRLF_LEN);
    if (status != NC_OK) {
		conn->err = ENOMEM;
        return status;
    }
	return NC_OK;
}
Exemple #5
0
/** Writes a message to the log file.
    @param type The type of message to log
    @param fileName The filename where this function was called from.
    @param lineNum The line number in the file where this function was called from.
    @param funcName The name of the function which called this function.
    @param msg The message to log
*/
void msg_details(msg_type type, const char *fileName, int lineNum, const char *funcName, const char *msg, ...)
{
	msg_init();
	
	/* Construct a string for the user's message */
	char msgbuf[1024];
	va_list args;
	va_start(args, msg);
	vsnprintf(msgbuf, 1024, msg, args);
	va_end(args);

	/* Remove any newlines at the end of the message. */
	int msgbufidx = strlen(msgbuf)-1;
	while(msgbuf[msgbufidx] == '\n')
	{
		msgbuf[msgbufidx] = '\0';
		msgbufidx--;
	}
	
	/* info to prepend to message printed to console */
	char typestr[1024];
	msg_type_string(type, typestr, 1024);

	/* Determine the stream that we are going to print out to: stdout,
	 * stderr, or don't print to console */
	FILE *stream = stdout;
	if(type == ERROR || type == FATAL)
		stream = stderr;
	if(msg_show_type(type) == 0)
		stream = NULL;

	char timestamp[1024];
	msg_timestamp(timestamp, 1024);
	char *fileNameCopy = strdup(fileName);
	char *shortFileName = basename(fileNameCopy);
	
	/* Print the message to stderr or stdout */
	if(stream)
	{
		// If using a non-standard logfile name, prepend the name to
		// the message. This makes it easier to distinguish between
		// which process is creating which message if there are
		// multiple programs running at once.
		char prepend[1024];
		if(strcmp(logfile, "log.txt") == 0)
			prepend[0] = '\0';
		else
			snprintf(prepend, 1024, "(%s) ", logfile);
		
		msg_start_color(type, stream);
		/* Print additional details to console for fatal errors */
		if(type == FATAL)
		{
			fprintf(stream, "%s %s%s\n", typestr, prepend, msgbuf);
			fprintf(stream, "%s %sOccurred at %s:%d in the function %s()\n",
			        typestr, prepend, shortFileName, lineNum, funcName);
		}
		else
			fprintf(stream, "%s %s%s\n", typestr, prepend, msgbuf);
		msg_end_color(type, stream);
	}

	// Not using funcName to try to keep log shorter.
	fprintf(f, "%s%s %12s:%-4d %s\n", typestr, timestamp, shortFileName, lineNum, msgbuf);
	free(fileNameCopy);

	/* Ensure messages are written to the file or console. */
	fflush(stream);
	fflush(f);
}
static int check_response(redis_node *rnode, struct msg *r)
{
    int ret;
    rmtContext *ctx = rnode->ctx;
    struct msg *resp, *msg = NULL;
    check_data *chdata;
    check_unit *cunit;
    char extra_err[50];
    struct array args;
    sds *arg;
    struct array *bulks1 = NULL, *bulks2 = NULL;
    sds *bulk1, *bulk2;

    if (r == NULL) {
        return RMT_ERROR;
    }

    extra_err[0] = '\0';

    resp = r->peer;
    r->peer = NULL;
    array_init(&args, 3, sizeof(sds));

    ASSERT(r->request && r->sent);
    ASSERT(resp != NULL && resp->request == 0);

    cunit = (check_unit *)r->ptr;
    chdata = cunit->cdata->data;

    if(resp->type == MSG_RSP_REDIS_ERROR){
        log_warn("Response from node[%s] for %s is error",
            rnode->addr, msg_type_string(r->type));
        goto error;
    }

    if (cunit->state == CHECK_UNIT_STATE_GET_KEY) {
        ASSERT(cunit->key == NULL);
        ASSERT(cunit->key_type == -1);
        ASSERT(cunit->result1 == NULL && cunit->result2 == NULL);
        ASSERT(cunit->srnode == rnode);

        if (resp->type != MSG_RSP_REDIS_BULK) {
            log_error("ERROR: the response type for command 'randomkey' from node[%s] is error: %s", 
                rnode->addr, msg_type_string(resp->type));
            goto error;
        }

        if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_BULK_NULL, 
            rmt_strlen(REDIS_REPLY_BULK_NULL)) == 0) {
            /* source group may have no keys, stop it */
            cunit->cdata->keys_count --;
            goto done;
        }

        cunit->key = redis_msg_response_get_bulk_string(resp);
        if (cunit->key == NULL) {
            log_error("ERROR: get bulk string from response of node[%s] failed, "
                "bulk_len: %"PRIu32", bulk_start: %p", 
                rnode->addr, resp->bulk_len, resp->bulk_start);
            goto error;
        }

        if (ctx->filter != NULL && !stringmatchlen(ctx->filter, sdslen(ctx->filter), 
            cunit->key, sdslen(cunit->key), 0)) {
            goto done;
        }

        ASSERT(sdslen(cunit->key) == resp->bulk_len);

        msg = msg_get(r->mb, 1, REDIS_DATA_TYPE_CMD);
        if (msg == NULL) {
            log_error("ERROR: out of memory.");
            goto error;
        }

        arg = array_push(&args);
        *arg = sdsnew("type");
        arg = array_push(&args);
        *arg = sdsdup(cunit->key);
        ret = redis_msg_append_command_full_safe(msg, &args);
        if (ret != RMT_OK) {
            log_error("ERROR: msg append multi bulk len failed.");
            goto error;
        }
        while (array_n(&args) > 0) {
            arg = array_pop(&args);
            sdsfree(*arg);
        }
        
        msg->ptr = cunit;
        msg->resp_check = check_response;

        ret = prepare_send_msg(rnode, msg, rnode);
        if (ret != RMT_OK) {
            log_error("ERROR: prepare send msg node[%s] failed.", rnode->addr);
            goto error;
        }

        cunit->state = CHECK_UNIT_STATE_GET_TYPE;
        goto next_step;
    }

    if (cunit->state == CHECK_UNIT_STATE_GET_TYPE) {
        ASSERT(cunit->key != NULL);
        ASSERT(cunit->key_type == -1);
        ASSERT(cunit->result1 == NULL && cunit->result2 == NULL);
        ASSERT(cunit->srnode == rnode);
        
        if (resp->type != MSG_RSP_REDIS_STATUS) {
            log_error("ERROR: the response type for command 'type' from node[%s] is error: %s", 
                rnode->addr, msg_type_string(resp->type));
            goto error;
        }

        if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_NONE, 
            rmt_strlen(REDIS_REPLY_STATUS_NONE)) == 0) {
            /* This key doesn't exit, may be expired or evicted */
            goto done;
        }

        msg = msg_get(r->mb, 1, REDIS_DATA_TYPE_CMD);
        if (msg == NULL) {
            log_error("ERROR: out of memory.");
            goto error;
        }

        if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_STRING, 
            rmt_strlen(REDIS_REPLY_STATUS_STRING)) == 0) {
            cunit->key_type = REDIS_STRING;

            arg = array_push(&args);
            *arg = sdsnew("get");
            arg = array_push(&args);
            *arg = sdsdup(cunit->key);
            ret = redis_msg_append_command_full_safe(msg, &args);
            if (ret != RMT_OK) {
                log_error("ERROR: msg append multi bulk len failed.");
                goto error;
            }
            while (array_n(&args) > 0) {
                arg = array_pop(&args);
                sdsfree(*arg);
            }
        } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_LIST, 
            rmt_strlen(REDIS_REPLY_STATUS_LIST)) == 0) {
            cunit->key_type = REDIS_LIST;

            arg = array_push(&args);
            *arg = sdsnew("lrange");
            arg = array_push(&args);
            *arg = sdsdup(cunit->key);
            arg = array_push(&args);
            *arg = sdsnew("0");
            arg = array_push(&args);
            *arg = sdsnew("-1");
            ret = redis_msg_append_command_full_safe(msg, &args);
            if (ret != RMT_OK) {
                log_error("ERROR: msg append multi bulk len failed.");
                goto error;
            }
            while (array_n(&args) > 0) {
                arg = array_pop(&args);
                sdsfree(*arg);
            }
        } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_SET, 
            rmt_strlen(REDIS_REPLY_STATUS_SET)) == 0) {
            cunit->key_type = REDIS_SET;

            arg = array_push(&args);
            *arg = sdsnew("smembers");
            arg = array_push(&args);
            *arg = sdsdup(cunit->key);
            ret = redis_msg_append_command_full_safe(msg, &args);
            if (ret != RMT_OK) {
                log_error("ERROR: msg append multi bulk len failed.");
                goto error;
            }
            while (array_n(&args) > 0) {
                arg = array_pop(&args);
                sdsfree(*arg);
            }
        } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_ZSET, 
            rmt_strlen(REDIS_REPLY_STATUS_ZSET)) == 0) {
            cunit->key_type = REDIS_ZSET;

            arg = array_push(&args);
            *arg = sdsnew("zrange");
            arg = array_push(&args);
            *arg = sdsdup(cunit->key);
            arg = array_push(&args);
            *arg = sdsnew("0");
            arg = array_push(&args);
            *arg = sdsnew("-1");
            ret = redis_msg_append_command_full_safe(msg, &args);
            if (ret != RMT_OK) {
                log_error("ERROR: msg append multi bulk len failed.");
                goto error;
            }
            while (array_n(&args) > 0) {
                arg = array_pop(&args);
                sdsfree(*arg);
            }
        } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_HASH, 
            rmt_strlen(REDIS_REPLY_STATUS_HASH)) == 0) {
            cunit->key_type = REDIS_HASH;

            arg = array_push(&args);
            *arg = sdsnew("hgetall");
            arg = array_push(&args);
            *arg = sdsdup(cunit->key);
            ret = redis_msg_append_command_full_safe(msg, &args);
            if (ret != RMT_OK) {
                log_error("ERROR: msg append multi bulk len failed.");
                goto error;
            }
            while (array_n(&args) > 0) {
                arg = array_pop(&args);
                sdsfree(*arg);
            }
        } else {
            log_error("ERROR: response key type from node[%s] is error: ",
                rnode->addr);
            goto error;
        }

        msg->ptr = cunit;
        msg->resp_check = check_response;
        
        ret = send_msg_to_all(cunit, msg);
        if (ret != RMT_OK) {
            log_error("ERROR: send msg to source and target group failed.");
            goto error;
        }

        cunit->state = CHECK_UNIT_STATE_GET_VALUE;
        goto next_step;
    }

    if (cunit->state == CHECK_UNIT_STATE_GET_VALUE) {
        ASSERT(cunit->key != NULL);
        ASSERT(cunit->key_type >= 0);
        ASSERT(cunit->result1 == NULL || cunit->result2 == NULL);
        
        if (cunit->key_type == REDIS_STRING) {
            if (resp->type != MSG_RSP_REDIS_BULK) {
                log_error("ERROR: the response type for %s from node[%s] is error: %s", 
                    rnode->addr, msg_type_string(r->type), msg_type_string(resp->type));
                goto error;
            }
        } else if (cunit->key_type == REDIS_LIST) {

        } else if (cunit->key_type == REDIS_SET) {
            
        } else if (cunit->key_type == REDIS_ZSET) {
            
        } else if (cunit->key_type == REDIS_HASH) {
            
        } else {
            NOT_REACHED();
        }

        if (cunit->result1 == NULL) {
            cunit->result1 = resp;
            resp = NULL;
        } else if (cunit->result2 == NULL) {
            cunit->result2 = resp;
            resp = NULL;
        } else {
            NOT_REACHED();
        }
    
        if (cunit->result1 != NULL && cunit->result2 != NULL) {
            if (cunit->key_type == REDIS_SET) {
                uint32_t j;

                bulks1 = get_multi_bulk_array_from_mbuf_list(cunit->result1->data);
                bulks2 = get_multi_bulk_array_from_mbuf_list(cunit->result2->data);
                if (bulks1 == NULL || bulks2 == NULL) {
                    log_error("ERROR: get multi bulk array from mbufs failed");
                    goto error;
                }

                if (array_n(bulks1) != array_n(bulks2)) {
                    chdata->err_inconsistent_value_keys_count ++;
                    rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0");
                    goto error;
                }
                
                array_sort(bulks1, string_binary_cmp);
                array_sort(bulks2, string_binary_cmp);

                for (j = 0; j < array_n(bulks1); j ++) {
                    bulk1 = array_get(bulks1, j);
                    bulk2 = array_get(bulks2, j);
                    if (string_binary_cmp(bulk1, bulk2) != 0) {
                        chdata->err_inconsistent_value_keys_count ++;
                        rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0");
                        goto error;
                    }
                }
            } else if (cunit->key_type == REDIS_HASH) {
                struct array *hash_datas1, *hash_datas2;
                uint32_t hash_len;
                uint32_t j;
                struct hash_data *hd1, *hd2;

                hash_datas1 = hash_datas2 = NULL;
            
                bulks1 = get_multi_bulk_array_from_mbuf_list(cunit->result1->data);
                bulks2 = get_multi_bulk_array_from_mbuf_list(cunit->result2->data);
                if (bulks1 == NULL || bulks2 == NULL) {
                    log_error("ERROR: get multi bulk array from mbufs failed");
                    goto error;
                }

                if (array_n(bulks1)%2 != 0 || array_n(bulks2)%2 != 0) {
                    log_error("ERROR: bad hash value");
                    goto error;
                }

                if (array_n(bulks1) != array_n(bulks2)) {
                    chdata->err_inconsistent_value_keys_count ++;
                    rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0");
                    goto error;
                }

                hash_len = array_n(bulks1)/2;
                hash_datas1 = array_create(hash_len, sizeof(struct hash_data));
                hash_datas2 = array_create(hash_len, sizeof(struct hash_data));

                for (j = 0; j < hash_len; j ++) {
                    hd1 = array_push(hash_datas1);
                    hd2 = array_push(hash_datas2);

                    bulk1 = array_pop(bulks1);
                    bulk2 = array_pop(bulks1);
                    hd1->field = *bulk1;
                    hd1->value = *bulk2;

                    bulk1 = array_pop(bulks2);
                    bulk2 = array_pop(bulks2);
                    hd2->field = *bulk1;
                    hd2->value = *bulk2;
                }

                array_sort(hash_datas1, hash_data_field_cmp);
                array_sort(hash_datas2, hash_data_field_cmp);

                for (j = 0; j < array_n(bulks1); j ++) {
                    hd1 = array_get(hash_datas1, j);
                    hd2 = array_get(hash_datas2, j);
                    if (string_binary_cmp(hd1->field, hd2->field) != 0) {
                        chdata->err_inconsistent_value_keys_count ++;
                        rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0");
                        if (hash_datas1 != NULL) {
                            while (array_n(hash_datas1) > 0) {
                                hd1 = array_pop(hash_datas1);
                                sdsfree(hd1->field);
                                sdsfree(hd1->value);
                            }
                            array_destroy(hash_datas1);
                            hash_datas1 = NULL;
                        }
                        if (hash_datas2 != NULL) {
                            while (array_n(hash_datas2) > 0) {
                                hd2 = array_pop(hash_datas2);
                                sdsfree(hd2->field);
                                sdsfree(hd2->value);
                            }
                            array_destroy(hash_datas2);
                            hash_datas2 = NULL;
                        }
                        goto error;
                    }
                    if (string_binary_cmp(hd1->value, hd2->value) != 0) {
                        chdata->err_inconsistent_value_keys_count ++;
                        rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0");
                        if (hash_datas1 != NULL) {
                            while (array_n(hash_datas1) > 0) {
                                hd1 = array_pop(hash_datas1);
                                sdsfree(hd1->field);
                                sdsfree(hd1->value);
                            }
                            array_destroy(hash_datas1);
                            hash_datas1 = NULL;
                        }
                        if (hash_datas2 != NULL) {
                            while (array_n(hash_datas2) > 0) {
                                hd2 = array_pop(hash_datas2);
                                sdsfree(hd2->field);
                                sdsfree(hd2->value);
                            }
                            array_destroy(hash_datas2);
                            hash_datas2 = NULL;
                        }
                        goto error;
                    }
                }

                if (hash_datas1 != NULL) {
                    while (array_n(hash_datas1) > 0) {
                        hd1 = array_pop(hash_datas1);
                        sdsfree(hd1->field);
                        sdsfree(hd1->value);
                    }
                    array_destroy(hash_datas1);
                    hash_datas1 = NULL;
                }
                if (hash_datas2 != NULL) {
                    while (array_n(hash_datas2) > 0) {
                        hd2 = array_pop(hash_datas2);
                        sdsfree(hd2->field);
                        sdsfree(hd2->value);
                    }
                    array_destroy(hash_datas2);
                    hash_datas2 = NULL;
                }
            } else if (msg_data_compare(cunit->result1, cunit->result2) != 0) {
                chdata->err_inconsistent_value_keys_count ++;
                rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0");
                goto error;
            }

            msg_put(cunit->result1);
            msg_free(cunit->result1);
            cunit->result1 = NULL;
            msg_put(cunit->result2);
            msg_free(cunit->result2);
            cunit->result2 = NULL;

            if (bulks1 != NULL) {
                while (array_n(bulks1) > 0) {
                    bulk1 = array_pop(bulks1);
                    sdsfree(*bulk1);
                }
                array_destroy(bulks1);
                bulks1 = NULL;
            }
            if (bulks2 != NULL) {
                while (array_n(bulks2) > 0) {
                    bulk2 = array_pop(bulks2);
                    sdsfree(*bulk2);
                }
                array_destroy(bulks2);
                bulks2 = NULL;
            }

            msg = msg_get(r->mb, 1, REDIS_DATA_TYPE_CMD);
            if (msg == NULL) {
                log_error("ERROR: out of memory.");
                goto error;
            }

            arg = array_push(&args);
            *arg = sdsnew("ttl");
            arg = array_push(&args);
            *arg = sdsdup(cunit->key);
            ret = redis_msg_append_command_full_safe(msg, &args);
            if (ret != RMT_OK) {
                log_error("ERROR: msg append multi bulk len failed.");
                goto error;
            }
            while (array_n(&args) > 0) {
                arg = array_pop(&args);
                sdsfree(*arg);
            }
            
            msg->ptr = cunit;
            msg->resp_check = check_response;
            
            ret = send_msg_to_all(cunit, msg);
            if (ret != RMT_OK) {
                log_error("ERROR: send msg to source and target group failed.");
                goto error;
            }
            cunit->state = CHECK_UNIT_STATE_GET_EXPIRE;
        }

        goto next_step;
    }

    if (cunit->state == CHECK_UNIT_STATE_GET_EXPIRE) {
        ASSERT(cunit->key != NULL);
        ASSERT(cunit->key_type >= 0);
        ASSERT(cunit->result1 == NULL || cunit->result2 == NULL);

        if (resp->type != MSG_RSP_REDIS_INTEGER) {
            log_error("ERROR: the response type for command 'ttl' from node[%s] is error: %s", 
                rnode->addr, msg_type_string(resp->type));
            goto error;
        }

        if (cunit->result1 == NULL) {
            cunit->result1 = resp;
            resp = NULL;
        } else if (cunit->result2 == NULL) {
            cunit->result2 = resp;
            resp = NULL;
        } else {
            NOT_REACHED();
        }

        if (cunit->result1 != NULL && cunit->result2 != NULL) {
            if (msg_data_compare(cunit->result1, cunit->result2) != 0) { 
                int mistake = (int)cunit->result1->integer - (int)cunit->result2->integer;
                ASSERT(mistake != 0);

                if (abs(mistake) > TTL_MISTAKE_CAN_BE_ACCEPT) {
                    chdata->err_inconsistent_expire_keys_count ++;
                    rmt_safe_snprintf(extra_err, 50, 
                        ", remaining time are %"PRIu32" and %"PRIu32"\0", 
                        cunit->result1->integer, cunit->result2->integer);
                    goto error;
                }
            }

            /* OK, this key is consistent between source group and target group */
            goto done;
        }

        goto next_step;
    }

done:

    check_unit_destroy(cunit);

next_step:
    
    msg_put(r);
    msg_free(r);
    if (resp != NULL) {
        msg_put(resp);
        msg_free(resp);
    }

    array_deinit(&args);
    
    return RMT_OK;

error:

    chdata->err_check_keys_count ++;

    if (cunit->key != NULL) {        
        log_error("ERROR: key checked failed: %s%s. key(len:%zu, type:%s): %.*s",  
            get_check_error(cunit), extra_err, 
            sdslen(cunit->key), get_redis_type_string(cunit->key_type),
            sdslen(cunit->key), cunit->key);
    } else {
        log_error("ERROR: key checked failed: %s%s.", 
            get_check_error(cunit), extra_err);
    }
    MSG_DUMP(r, LOG_ERR, 1);
    msg_put(r);
    msg_free(r);
    if (resp != NULL) {
        MSG_DUMP(resp, LOG_ERR, 1);
        msg_put(resp);
        msg_free(resp);
    }

    if (msg != NULL) {
        msg_put(msg);
        msg_free(msg);
    }

    check_unit_destroy(cunit);
    
    while (array_n(&args) > 0) {
        arg = array_pop(&args);
        sdsfree(*arg);
    }
    array_deinit(&args);

    if (bulks1 != NULL) {
        while (array_n(bulks1) > 0) {
            bulk1 = array_pop(bulks1);
            sdsfree(*bulk1);
        }
        array_destroy(bulks1);
        bulks1 = NULL;
    }
    if (bulks2 != NULL) {
        while (array_n(bulks2) > 0) {
            bulk2 = array_pop(bulks2);
            sdsfree(*bulk2);
        }
        array_destroy(bulks2);
        bulks2 = NULL;
    }
    
    return RMT_OK;
}