diff --git a/src/rdb.c b/src/rdb.c index f56e94b0a..7b3aff9db 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -3208,15 +3208,13 @@ void startLoadingFile(size_t size, char* filename, int rdbflags) { /* Refresh the absolute loading progress info */ void loadingAbsProgress(off_t pos) { server.loading_loaded_bytes = pos; - if (server.stat_peak_memory < zmalloc_used_memory()) - server.stat_peak_memory = zmalloc_used_memory(); + updatePeakMemory(zmalloc_used_memory()); } /* Refresh the incremental loading progress info */ void loadingIncrProgress(off_t size) { server.loading_loaded_bytes += size; - if (server.stat_peak_memory < zmalloc_used_memory()) - server.stat_peak_memory = zmalloc_used_memory(); + updatePeakMemory(zmalloc_used_memory()); } /* Update the file name currently being loaded */ diff --git a/src/server.c b/src/server.c index 2dc08f8bb..dadb6c838 100644 --- a/src/server.c +++ b/src/server.c @@ -1358,11 +1358,17 @@ void checkChildrenDone(void) { } } +/* Record the max memory used since the server was started. */ +void updatePeakMemory(size_t used_memory) { + if (unlikely(used_memory > server.stat_peak_memory)) { + server.stat_peak_memory = used_memory; + server.stat_peak_memory_time = server.unixtime; + } +} + /* Called from serverCron and cronUpdateMemoryStats to update cached memory metrics. */ void cronUpdateMemoryStats(void) { - /* Record the max memory used since the server was started. */ - if (zmalloc_used_memory() > server.stat_peak_memory) - server.stat_peak_memory = zmalloc_used_memory(); + updatePeakMemory(zmalloc_used_memory()); run_with_period(100) { /* Sample the RSS and other metrics here since this is a relatively slow call. @@ -1773,9 +1779,7 @@ extern int ProcessingEventsWhileBlocked; void beforeSleep(struct aeEventLoop *eventLoop) { UNUSED(eventLoop); - size_t zmalloc_used = zmalloc_used_memory(); - if (zmalloc_used > server.stat_peak_memory) - server.stat_peak_memory = zmalloc_used; + updatePeakMemory(zmalloc_used_memory()); /* Just call a subset of vital functions in case we are re-entering * the event loop from processEventsWhileBlocked(). Note that in this @@ -2894,6 +2898,7 @@ void initServer(void) { /* A few stats we don't want to reset: server startup time, and peak mem. */ server.stat_starttime = time(NULL); server.stat_peak_memory = 0; + server.stat_peak_memory_time = server.unixtime; server.stat_current_cow_peak = 0; server.stat_current_cow_bytes = 0; server.stat_current_cow_updated = 0; @@ -3915,9 +3920,7 @@ void call(client *c, int flags) { /* Record peak memory after each command and before the eviction that runs * before the next command. */ - size_t zmalloc_used = zmalloc_used_memory(); - if (zmalloc_used > server.stat_peak_memory) - server.stat_peak_memory = zmalloc_used; + updatePeakMemory(zmalloc_used_memory()); /* Do some maintenance job and cleanup */ afterCommand(c); @@ -5953,8 +5956,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { * may happen that the instantaneous value is slightly bigger than * the peak value. This may confuse users, so we update the peak * if found smaller than the current memory usage. */ - if (zmalloc_used > server.stat_peak_memory) - server.stat_peak_memory = zmalloc_used; + updatePeakMemory(zmalloc_used); bytesToHuman(hmem,sizeof(hmem),zmalloc_used); bytesToHuman(peak_hmem,sizeof(peak_hmem),server.stat_peak_memory); @@ -5973,6 +5975,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { "used_memory_rss_human:%s\r\n", used_memory_rss_hmem, "used_memory_peak:%zu\r\n", server.stat_peak_memory, "used_memory_peak_human:%s\r\n", peak_hmem, + "used_memory_peak_time:%jd\r\n", (intmax_t)server.stat_peak_memory_time, "used_memory_peak_perc:%.2f%%\r\n", mh->peak_perc, "used_memory_overhead:%zu\r\n", mh->overhead_total, "used_memory_startup:%zu\r\n", mh->startup_allocated, diff --git a/src/server.h b/src/server.h index 07b784d59..3e6524971 100644 --- a/src/server.h +++ b/src/server.h @@ -1877,6 +1877,7 @@ struct redisServer { long long stat_total_active_defrag_time; /* Total time memory fragmentation over the limit, unit us */ monotime stat_last_active_defrag_time; /* Timestamp of current active defrag start */ size_t stat_peak_memory; /* Max used memory record */ + time_t stat_peak_memory_time; /* Time when stat_peak_memory was recorded */ long long stat_aof_rewrites; /* number of aof file rewrites performed */ long long stat_aofrw_consecutive_failures; /* The number of consecutive failures of aofrw */ long long stat_rdb_saves; /* number of rdb saves performed */ @@ -3308,6 +3309,7 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec); /* Core functions */ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level); +void updatePeakMemory(size_t used_memory); size_t freeMemoryGetNotCountedMemory(void); int overMaxmemoryAfterAlloc(size_t moremem); uint64_t getCommandFlags(client *c); diff --git a/tests/unit/info.tcl b/tests/unit/info.tcl index b1e524607..9400e29d8 100644 --- a/tests/unit/info.tcl +++ b/tests/unit/info.tcl @@ -546,6 +546,21 @@ start_server {tags {"info" "external:skip"}} { assert_equal [dict get $mem_stats overhead.db.hashtable.rehashing] [expr $ht0_size * $ptr_size] assert_equal [dict get $mem_stats db.dict.rehashing.count] {1} } + + test {memory: used_memory_peak_time is updated when used_memory_peak is updated} { + r flushall + + # Add a large string to trigger memory peak tracking + set time_before_add_large_str [clock seconds] + r set large_str [string repeat "a" 1000000] + assert {[s used_memory_peak_time] >= $time_before_add_large_str} + set peak_value [s used_memory_peak] + + r del large_str + # Add a small string, which cannot exceed the previous peak value + r set small_str [string repeat "a" 1000] + assert {[s used_memory_peak] == $peak_value} + } } start_cluster 1 0 {tags {external:skip cluster}} {