mirror of https://github.com/redis/redis.git
				
				
				
			
		
			
				
	
	
		
			312 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			312 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);
 | |
| }
 | |
| 
 | |
| /* 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) {
 | |
|     REDISMODULE_NOT_USED(argv);
 | |
|     REDISMODULE_NOT_USED(argc);
 | |
| 
 | |
|     if (RedisModule_Init(ctx,"testhook",1,REDISMODULE_APIVER_1)
 | |
|         == REDISMODULE_ERR) 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);
 | |
| 
 | |
|     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;
 | |
| }
 | |
| 
 |