mirror of https://github.com/redis/redis.git
				
				
				
			
		
			
				
	
	
		
			335 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			335 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
/* This module is used to test the server events hooks API.
 | 
						|
 *
 | 
						|
 * -----------------------------------------------------------------------------
 | 
						|
 *
 | 
						|
 * Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
 | 
						|
 * All rights reserved.
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions are met:
 | 
						|
 *
 | 
						|
 *   * Redistributions of source code must retain the above copyright notice,
 | 
						|
 *     this list of conditions and the following disclaimer.
 | 
						|
 *   * Redistributions in binary form must reproduce the above copyright
 | 
						|
 *     notice, this list of conditions and the following disclaimer in the
 | 
						|
 *     documentation and/or other materials provided with the distribution.
 | 
						|
 *   * Neither the name of Redis nor the names of its contributors may be used
 | 
						|
 *     to endorse or promote products derived from this software without
 | 
						|
 *     specific prior written permission.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | 
						|
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 | 
						|
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | 
						|
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | 
						|
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
						|
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | 
						|
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | 
						|
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
						|
 * POSSIBILITY OF SUCH DAMAGE.
 | 
						|
 */
 | 
						|
 | 
						|
#include "redismodule.h"
 | 
						|
#include <stdio.h>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
/* We need to store events to be able to test and see what we got, and we can't
 | 
						|
 * store them in the key-space since that would mess up rdb loading (duplicates)
 | 
						|
 * and be lost of flushdb. */
 | 
						|
RedisModuleDict *event_log = NULL;
 | 
						|
 | 
						|
typedef struct EventElement {
 | 
						|
    long count;
 | 
						|
    RedisModuleString *last_val_string;
 | 
						|
    long last_val_int;
 | 
						|
} EventElement;
 | 
						|
 | 
						|
void LogStringEvent(RedisModuleCtx *ctx, const char* keyname, const char* data) {
 | 
						|
    EventElement *event = RedisModule_DictGetC(event_log, (void*)keyname, strlen(keyname), NULL);
 | 
						|
    if (!event) {
 | 
						|
        event = RedisModule_Alloc(sizeof(EventElement));
 | 
						|
        memset(event, 0, sizeof(EventElement));
 | 
						|
        RedisModule_DictSetC(event_log, (void*)keyname, strlen(keyname), event);
 | 
						|
    }
 | 
						|
    if (event->last_val_string) RedisModule_FreeString(ctx, event->last_val_string);
 | 
						|
    event->last_val_string = RedisModule_CreateString(ctx, data, strlen(data));
 | 
						|
    event->count++;
 | 
						|
}
 | 
						|
 | 
						|
void LogNumericEvent(RedisModuleCtx *ctx, const char* keyname, long data) {
 | 
						|
    REDISMODULE_NOT_USED(ctx);
 | 
						|
    EventElement *event = RedisModule_DictGetC(event_log, (void*)keyname, strlen(keyname), NULL);
 | 
						|
    if (!event) {
 | 
						|
        event = RedisModule_Alloc(sizeof(EventElement));
 | 
						|
        memset(event, 0, sizeof(EventElement));
 | 
						|
        RedisModule_DictSetC(event_log, (void*)keyname, strlen(keyname), event);
 | 
						|
    }
 | 
						|
    event->last_val_int = data;
 | 
						|
    event->count++;
 | 
						|
}
 | 
						|
 | 
						|
void FreeEvent(RedisModuleCtx *ctx, EventElement *event) {
 | 
						|
    if (event->last_val_string)
 | 
						|
        RedisModule_FreeString(ctx, event->last_val_string);
 | 
						|
    RedisModule_Free(event);
 | 
						|
}
 | 
						|
 | 
						|
int cmdEventCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
 | 
						|
{
 | 
						|
    if (argc != 2){
 | 
						|
        RedisModule_WrongArity(ctx);
 | 
						|
        return REDISMODULE_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    EventElement *event = RedisModule_DictGet(event_log, argv[1], NULL);
 | 
						|
    RedisModule_ReplyWithLongLong(ctx, event? event->count: 0);
 | 
						|
    return REDISMODULE_OK;
 | 
						|
}
 | 
						|
 | 
						|
int cmdEventLast(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
 | 
						|
{
 | 
						|
    if (argc != 2){
 | 
						|
        RedisModule_WrongArity(ctx);
 | 
						|
        return REDISMODULE_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    EventElement *event = RedisModule_DictGet(event_log, argv[1], NULL);
 | 
						|
    if (event && event->last_val_string)
 | 
						|
        RedisModule_ReplyWithString(ctx, event->last_val_string);
 | 
						|
    else if (event)
 | 
						|
        RedisModule_ReplyWithLongLong(ctx, event->last_val_int);
 | 
						|
    else
 | 
						|
        RedisModule_ReplyWithNull(ctx);
 | 
						|
    return REDISMODULE_OK;
 | 
						|
}
 | 
						|
 | 
						|
void clearEvents(RedisModuleCtx *ctx)
 | 
						|
{
 | 
						|
    RedisModuleString *key;
 | 
						|
    EventElement *event;
 | 
						|
    RedisModuleDictIter *iter = RedisModule_DictIteratorStart(event_log, "^", NULL);
 | 
						|
    while((key = RedisModule_DictNext(ctx, iter, (void**)&event)) != NULL) {
 | 
						|
        event->count = 0;
 | 
						|
        event->last_val_int = 0;
 | 
						|
        if (event->last_val_string) RedisModule_FreeString(ctx, event->last_val_string);
 | 
						|
        event->last_val_string = NULL;
 | 
						|
        RedisModule_DictDel(event_log, key, NULL);
 | 
						|
        RedisModule_Free(event);
 | 
						|
    }
 | 
						|
    RedisModule_DictIteratorStop(iter);
 | 
						|
}
 | 
						|
 | 
						|
int cmdEventsClear(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(argc);
 | 
						|
    REDISMODULE_NOT_USED(argv);
 | 
						|
    clearEvents(ctx);
 | 
						|
    return REDISMODULE_OK;
 | 
						|
}
 | 
						|
 | 
						|
/* Client state change callback. */
 | 
						|
void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
 | 
						|
    RedisModuleClientInfo *ci = data;
 | 
						|
    char *keyname = (sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
 | 
						|
        "client-connected" : "client-disconnected";
 | 
						|
    LogNumericEvent(ctx, keyname, ci->id);
 | 
						|
}
 | 
						|
 | 
						|
void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
 | 
						|
    RedisModuleFlushInfo *fi = data;
 | 
						|
    char *keyname = (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) ?
 | 
						|
        "flush-start" : "flush-end";
 | 
						|
    LogNumericEvent(ctx, keyname, fi->dbnum);
 | 
						|
}
 | 
						|
 | 
						|
void roleChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(data);
 | 
						|
 | 
						|
    RedisModuleReplicationInfo *ri = data;
 | 
						|
    char *keyname = (sub == REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER) ?
 | 
						|
        "role-master" : "role-replica";
 | 
						|
    LogStringEvent(ctx, keyname, ri->masterhost);
 | 
						|
}
 | 
						|
 | 
						|
void replicationChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(data);
 | 
						|
 | 
						|
    char *keyname = (sub == REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE) ?
 | 
						|
        "replica-online" : "replica-offline";
 | 
						|
    LogNumericEvent(ctx, keyname, 0);
 | 
						|
}
 | 
						|
 | 
						|
void rasterLinkChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(data);
 | 
						|
 | 
						|
    char *keyname = (sub == REDISMODULE_SUBEVENT_MASTER_LINK_UP) ?
 | 
						|
        "masterlink-up" : "masterlink-down";
 | 
						|
    LogNumericEvent(ctx, keyname, 0);
 | 
						|
}
 | 
						|
 | 
						|
void persistenceCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(data);
 | 
						|
 | 
						|
    char *keyname = NULL;
 | 
						|
    switch (sub) {
 | 
						|
        case REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START: keyname = "persistence-rdb-start"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START: keyname = "persistence-aof-start"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START: keyname = "persistence-syncrdb-start"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_PERSISTENCE_ENDED: keyname = "persistence-end"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_PERSISTENCE_FAILED: keyname = "persistence-failed"; break;
 | 
						|
    }
 | 
						|
    /* modifying the keyspace from the fork child is not an option, using log instead */
 | 
						|
    RedisModule_Log(ctx, "warning", "module-event-%s", keyname);
 | 
						|
    if (sub == REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START)
 | 
						|
        LogNumericEvent(ctx, keyname, 0);
 | 
						|
}
 | 
						|
 | 
						|
void loadingCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(data);
 | 
						|
 | 
						|
    char *keyname = NULL;
 | 
						|
    switch (sub) {
 | 
						|
        case REDISMODULE_SUBEVENT_LOADING_RDB_START: keyname = "loading-rdb-start"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_LOADING_AOF_START: keyname = "loading-aof-start"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_LOADING_REPL_START: keyname = "loading-repl-start"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_LOADING_ENDED: keyname = "loading-end"; break;
 | 
						|
        case REDISMODULE_SUBEVENT_LOADING_FAILED: keyname = "loading-failed"; break;
 | 
						|
    }
 | 
						|
    LogNumericEvent(ctx, keyname, 0);
 | 
						|
}
 | 
						|
 | 
						|
void loadingProgressCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
 | 
						|
    RedisModuleLoadingProgress *ei = data;
 | 
						|
    char *keyname = (sub == REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB) ?
 | 
						|
        "loading-progress-rdb" : "loading-progress-aof";
 | 
						|
    LogNumericEvent(ctx, keyname, ei->progress);
 | 
						|
}
 | 
						|
 | 
						|
void shutdownCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(data);
 | 
						|
    REDISMODULE_NOT_USED(sub);
 | 
						|
 | 
						|
    RedisModule_Log(ctx, "warning", "module-event-%s", "shutdown");
 | 
						|
}
 | 
						|
 | 
						|
void cronLoopCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(sub);
 | 
						|
 | 
						|
    RedisModuleCronLoop *ei = data;
 | 
						|
    LogNumericEvent(ctx, "cron-loop", ei->hz);
 | 
						|
}
 | 
						|
 | 
						|
void moduleChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
 | 
						|
    RedisModuleModuleChange *ei = data;
 | 
						|
    char *keyname = (sub == REDISMODULE_SUBEVENT_MODULE_LOADED) ?
 | 
						|
        "module-loaded" : "module-unloaded";
 | 
						|
    LogStringEvent(ctx, keyname, ei->module_name);
 | 
						|
}
 | 
						|
 | 
						|
void swapDbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
 | 
						|
{
 | 
						|
    REDISMODULE_NOT_USED(e);
 | 
						|
    REDISMODULE_NOT_USED(sub);
 | 
						|
 | 
						|
    RedisModuleSwapDbInfo *ei = data;
 | 
						|
    LogNumericEvent(ctx, "swapdb-first", ei->dbnum_first);
 | 
						|
    LogNumericEvent(ctx, "swapdb-second", ei->dbnum_second);
 | 
						|
}
 | 
						|
 | 
						|
/* This function must be present on each Redis module. It is used in order to
 | 
						|
 * register the commands into the Redis server. */
 | 
						|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
 | 
						|
#define VerifySubEventSupported(e, s) \
 | 
						|
    if (!RedisModule_IsSubEventSupported(e, s)) { \
 | 
						|
        return REDISMODULE_ERR; \
 | 
						|
    }
 | 
						|
 | 
						|
    REDISMODULE_NOT_USED(argv);
 | 
						|
    REDISMODULE_NOT_USED(argc);
 | 
						|
 | 
						|
    if (RedisModule_Init(ctx,"testhook",1,REDISMODULE_APIVER_1)
 | 
						|
        == REDISMODULE_ERR) return REDISMODULE_ERR;
 | 
						|
 | 
						|
    /* Example on how to check if a server sub event is supported */
 | 
						|
    if (!RedisModule_IsSubEventSupported(RedisModuleEvent_ReplicationRoleChanged, REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER)) {
 | 
						|
        return REDISMODULE_ERR;
 | 
						|
    }
 | 
						|
 | 
						|
    /* replication related hooks */
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_ReplicationRoleChanged, roleChangeCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_ReplicaChange, replicationChangeCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_MasterLinkChange, rasterLinkChangeCallback);
 | 
						|
 | 
						|
    /* persistence related hooks */
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_Persistence, persistenceCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_Loading, loadingCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_LoadingProgress, loadingProgressCallback);
 | 
						|
 | 
						|
    /* other hooks */
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_ClientChange, clientChangeCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_FlushDB, flushdbCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_Shutdown, shutdownCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_CronLoop, cronLoopCallback);
 | 
						|
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_ModuleChange, moduleChangeCallback);
 | 
						|
    RedisModule_SubscribeToServerEvent(ctx,
 | 
						|
        RedisModuleEvent_SwapDB, swapDbCallback);
 | 
						|
 | 
						|
    event_log = RedisModule_CreateDict(ctx);
 | 
						|
 | 
						|
    if (RedisModule_CreateCommand(ctx,"hooks.event_count", cmdEventCount,"",0,0,0) == REDISMODULE_ERR)
 | 
						|
        return REDISMODULE_ERR;
 | 
						|
    if (RedisModule_CreateCommand(ctx,"hooks.event_last", cmdEventLast,"",0,0,0) == REDISMODULE_ERR)
 | 
						|
        return REDISMODULE_ERR;
 | 
						|
    if (RedisModule_CreateCommand(ctx,"hooks.clear", cmdEventsClear,"",0,0,0) == REDISMODULE_ERR)
 | 
						|
        return REDISMODULE_ERR;
 | 
						|
 | 
						|
    return REDISMODULE_OK;
 | 
						|
}
 | 
						|
 | 
						|
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
 | 
						|
    clearEvents(ctx);
 | 
						|
    RedisModule_FreeDict(ctx, event_log);
 | 
						|
    event_log = NULL;
 | 
						|
    return REDISMODULE_OK;
 | 
						|
}
 | 
						|
 |