/*
Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com>
This file is part of GlusterFS.
This file is licensed to you under your choice of the GNU Lesser
General Public License, version 3 or any later version (LGPLv3 or
later), or the GNU General Public License, version 2 (GPLv2), in all
cases as published by the Free Software Foundation.
*/
#ifndef _MEM_POOL_H_
#define _MEM_POOL_H_
#include "glusterfs/list.h"
#include "glusterfs/locking.h"
#include "glusterfs/atomic.h"
#include "glusterfs/logging.h"
#include "glusterfs/mem-types.h"
#include "glusterfs/glusterfs.h" /* for glusterfs_ctx_t */
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <stdarg.h>
/*
* Need this for unit tests since inline functions
* access memory allocation and need to use the
* unit test versions
*/
#ifdef UNIT_TESTING
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#endif
#define GF_MEM_TRAILER_SIZE 8
#define GF_MEM_HEADER_MAGIC 0xCAFEBABE
#define GF_MEM_TRAILER_MAGIC 0xBAADF00D
#define GF_MEM_INVALID_MAGIC 0xDEADC0DE
#define POOL_SMALLEST 7 /* i.e. 128 */
#define POOL_LARGEST 20 /* i.e. 1048576 */
#define NPOOLS (POOL_LARGEST - POOL_SMALLEST + 1)
struct mem_acct_rec {
const char *typestr;
uint64_t size;
uint64_t max_size;
uint64_t total_allocs;
uint32_t num_allocs;
uint32_t max_num_allocs;
gf_lock_t lock;
#ifdef DEBUG
struct list_head obj_list;
#endif
};
struct mem_acct {
uint32_t num_types;
gf_atomic_t refcnt;
struct mem_acct_rec rec[0];
};
struct mem_header {
uint32_t type;
size_t size;
struct mem_acct *mem_acct;
uint32_t magic;
#ifdef DEBUG
struct list_head acct_list;
#endif
int padding[8];
};
#define GF_MEM_HEADER_SIZE (sizeof(struct mem_header))
#ifdef DEBUG
struct mem_invalid {
uint32_t magic;
void *mem_acct;
uint32_t type;
size_t size;
void *baseaddr;
};
#endif
void *
__gf_calloc(size_t cnt, size_t size, uint32_t type, const char *typestr);
void *
__gf_malloc(size_t size, uint32_t type, const char *typestr);
void *
__gf_realloc(void *ptr, size_t size);
int
gf_vasprintf(char **string_ptr, const char *format, va_list arg);
int
gf_asprintf(char **string_ptr, const char *format, ...)
__attribute__((__format__(__printf__, 2, 3)));
void
__gf_free(void *ptr);
static inline void *
__gf_default_malloc(size_t size)
{
void *ptr = NULL;
ptr = malloc(size);
if (!ptr)
gf_msg_nomem("", GF_LOG_ALERT, size);
return ptr;
}
static inline void *
__gf_default_calloc(int cnt, size_t size)
{
void *ptr = NULL;
ptr = calloc(cnt, size);
if (!ptr)
gf_msg_nomem("", GF_LOG_ALERT, (cnt * size));
return ptr;
}
static inline void *
__gf_default_realloc(void *oldptr, size_t size)
{
void *ptr = NULL;
ptr = realloc(oldptr, size);
if (!ptr)
gf_msg_nomem("", GF_LOG_ALERT, size);
return ptr;
}
#define MALLOC(size) __gf_default_malloc(size)
#define CALLOC(cnt, size) __gf_default_calloc(cnt, size)
#define REALLOC(ptr, size) __gf_default_realloc(ptr, size)
#define FREE(ptr) \
do { \
if (ptr != NULL) { \
free((void *)ptr); \
ptr = (void *)0xeeeeeeee; \
} \
} while (0)
#define GF_CALLOC(nmemb, size, type) __gf_calloc(nmemb, size, type, #type)
#define GF_MALLOC(size, type) __gf_malloc(size, type, #type)
#define GF_REALLOC(ptr, size) __gf_realloc(ptr, size)
#define GF_FREE(free_ptr) __gf_free(free_ptr)
static inline char *
gf_strndup(const char *src, size_t len)
{
char *dup_str = NULL;
if (!src) {
goto out;
}
dup_str = GF_MALLOC(len + 1, gf_common_mt_strdup);
if (!dup_str) {
goto out;
}
memcpy(dup_str, src, len);
dup_str[len] = '\0';
out:
return dup_str;
}
static inline char *
gf_strdup(const char *src)
{
if (!src)
return NULL;
return gf_strndup(src, strlen(src));
}
static inline void *
gf_memdup(const void *src, size_t size)
{
void *dup_mem = NULL;
dup_mem = GF_MALLOC(size, gf_common_mt_strdup);
if (!dup_mem)
goto out;
memcpy(dup_mem, src, size);
out:
return dup_mem;
}
/* kind of 'header' for the actual mem_pool_shared structure, this might make
* it possible to dump some more details in a statedump */
struct mem_pool {
/* object size, without pooled_obj_hdr_t */
unsigned long sizeof_type;
unsigned long count; /* requested pool size (unused) */
char *name;
gf_atomic_t active; /* current allocations */
#ifdef DEBUG
gf_atomic_t hit; /* number of allocations served from pt_pool */
gf_atomic_t miss; /* number of std allocs due to miss */
#endif
struct list_head owner; /* glusterfs_ctx_t->mempool_list */
glusterfs_ctx_t *ctx; /* take ctx->lock when updating owner */
struct mem_pool_shared *pool; /* the initial pool that was returned */
};
typedef struct pooled_obj_hdr {
unsigned long magic;
struct pooled_obj_hdr *next;
struct per_thread_pool_list *pool_list;
unsigned int power_of_two;
/* track the pool that was used to request this object */
struct mem_pool *pool;
} pooled_obj_hdr_t;
#define AVAILABLE_SIZE(p2) (1 << (p2))
typedef struct per_thread_pool {
/* the pool that was used to request this allocation */
struct mem_pool_shared *parent;
/* Everything else is protected by our own lock. */
pooled_obj_hdr_t *hot_list;
pooled_obj_hdr_t *cold_list;
} per_thread_pool_t;
typedef struct per_thread_pool_list {
/*
* These first two members are protected by the global pool lock. When
* a thread first tries to use any pool, we create one of these. We
* link it into the global list using thr_list so the pool-sweeper
* thread can find it, and use pthread_setspecific so this thread can
* find it. When the per-thread destructor runs, we "poison" the pool
* list to prevent further allocations. This also signals to the
* pool-sweeper thread that the list should be detached and freed after
* the next time it's swept.
*/
struct list_head thr_list;
unsigned int poison;
/*
* There's really more than one pool, but the actual number is hidden
* in the implementation code so we just make it a single-element array
* here.
*/
pthread_spinlock_t lock;
per_thread_pool_t pools[1];
} per_thread_pool_list_t;
/* actual pool structure, shared between different mem_pools */
struct mem_pool_shared {
unsigned int power_of_two;
/*
* Updates to these are *not* protected by a global lock, so races
* could occur and the numbers might be slightly off. Don't expect
* them to line up exactly. It's the general trends that matter, and
* it's not worth the locked-bus-cycle overhead to make these precise.
*/
gf_atomic_t allocs_hot;
gf_atomic_t allocs_cold;
gf_atomic_t allocs_stdc;
gf_atomic_t frees_to_list;
};
void
mem_pools_init_early(void); /* basic initialization of memory pools */
void
mem_pools_init_late(void); /* start the pool_sweeper thread */
void
mem_pools_fini(void); /* cleanup memory pools */
struct mem_pool *
mem_pool_new_fn(glusterfs_ctx_t *ctx, unsigned long sizeof_type,
unsigned long count, char *name);
#define mem_pool_new(type, count) \
mem_pool_new_fn(THIS->ctx, sizeof(type), count, #type)
#define mem_pool_new_ctx(ctx, type, count) \
mem_pool_new_fn(ctx, sizeof(type), count, #type)
void
mem_put(void *ptr);
void *
mem_get(struct mem_pool *pool);
void *
mem_get0(struct mem_pool *pool);
void
mem_pool_destroy(struct mem_pool *pool);
void
gf_mem_acct_enable_set(void *ctx);
/* hit will be set to :
* _gf_true if the memory is served from mem pool
* _gf_false if the requested size was not present in mem pool and hence
* std alloc'd.
*/
void *
mem_pool_get(unsigned long sizeof_type, gf_boolean_t *hit);
void *
mem_pool_get0(unsigned long sizeof_type, gf_boolean_t *hit);
#endif /* _MEM_POOL_H */