/******************************************************************************
 * file    lib/management/memory.c 
 * author  YuLiang
 * version 1.0.0
 * date    14-Sep-2021
 * brief   This file provides all the memory operation functions.
 ******************************************************************************
 * Attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2021 LandPower</center></h2>
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *   2. 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.
 *   3. Neither the name of LandPower 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 HOLDER 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.
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* 标准C库头文件. */
#include <malloc.h>
#include <sys/resource.h>

/* 用户代码头文件. */
#include "cmd.h"

/* Private define ------------------------------------------------------------*/
#define MEM_LOCK pthread_mutex_lock(&mem_mutex)
#define MEM_UNLOCK pthread_mutex_unlock(&mem_mutex)

/* Private macro -------------------------------------------------------------*/

/* Private typedef -----------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
static mem_node_t mem_list_management[] =
{
    {MTYPE_CLI,             "Cli"},
    {MTYPE_BUF,             "Buffer"},
    {MTYPE_BUF_DATA,        "Buffer data"},
    {MTYPE_BUF_TMP,         "Buffer temp"},
    {MTYPE_VTY,             "Vty"},
    {MTYPE_VTY_TMP,         "Vty temp"},
    {MTYPE_VTY_OUT_BUF,     "Vty output buffer"},
    {MTYPE_VTY_HIST,        "Vty history"},
    {MTYPE_HASH,            "Hash"},
    {MTYPE_HASH_BACKET,     "Hash Bucket"},
    {MTYPE_HASH_INDEX,      "Hash Index"},
    {MTYPE_LOG,             "Logging"},
    {MTYPE_MTIMER,          "Timer"},
    {MTYPE_THREAD_MONITOR,  "Thread monitor"},
    {MTYPE_PREFIX,          "Prefix"},
    {MTYPE_THREAD,          "Thread"},
    {MTYPE_THREAD_STATS,    "Thread stats"},
    {MTYPE_THREAD_MASTER,   "Thread master"},
    {MTYPE_THREAD_FUNCNAME, "Thread function name"},
    {MTYPE_FIFO,            "fifo"},
    {MTYPE_GPIO,            "gpio"},
    { -1, NULL },
};

static mem_node_t mem_list_process[] =
{
    { -1, NULL },
};

static mem_list_t mlists[] __attribute__ ((unused)) =
{
    {mem_list_management,   "MANAGEMENT"},
    {mem_list_process,      "PROCESS"},
    {NULL, NULL},
};

/* 同于统计每个模块申请内存的次数. */
static struct 
{
    const char *name;
    int32_t alloc;
    uint32_t t_malloc;
    uint32_t c_malloc;
    uint32_t t_calloc;
    uint32_t c_calloc;
    uint32_t t_realloc;
    uint32_t t_free;
    uint32_t c_strdup;
} mstat[MTYPE_MAX];

static pthread_mutex_t mem_mutex;

/* Private function prototypes -----------------------------------------------*/

/* Internal functions --------------------------------------------------------*/
/* 查找模块相应的说明字符串: key -- 模块 */
static const char *_x_desc_lookup(uint32_t key)
{
    const mem_list_t *list = NULL;
    const mem_node_t *pnt = NULL;

    for(list = mlists; list->nodes != NULL; list++)
    {
        for(pnt = list->nodes; pnt->index >= 0; pnt++)
        {
            if (pnt->index == key) 
            {
                return pnt->format;
            }
        }
    }

    return "";
}

/* 打印每个模块的内存使用情况: pri -- log优先级 */
static void _x_memstats_print(int32_t pri)
{
    mem_list_t *ml = NULL;

    for (ml = mlists; ml->nodes; ml++)
    {
        mem_node_t *m = NULL;

        log_out(LOG_MEMORY, pri, "Memory utilization in module %s:", ml->name);
        for(m = ml->nodes; m->index >= 0; m++)
        {
            if (m->index && mstat[m->index].alloc)
            {
                log_out(LOG_MEMORY, pri, "  %-30s: %10ld", m->format, (long)mstat[m->index].alloc);
            }
        }
    }
}

/* 打印内存申请错误信息: fname -- 调用的函数，type -- 模块， size -- 大小 */
static void __attribute__ ((noreturn)) _x_mem_error(const char *fname, int32_t type, size_t size)
{
    log_err(LOG_MEMORY, "%s : can't allocate memory for '%s' size %d: %s!", 
        fname, _x_desc_lookup(type), (int)size, safe_strerror(errno));

    /* 打印每个模块的内存使用请款 */
    _x_memstats_print(LOG_LVL_WARN);

    /* 打印堆栈信息 */
    log_backtrace(LOG_LVL_WARN);
    
    abort();
}

/* 增加内存alloc计数 */
static void _x_alloc_inc(int32_t type)
{
    MEM_LOCK;
    mstat[type].alloc++;
    MEM_UNLOCK;
}

/* 减少内存alloc计数 */
static void _x_alloc_dec(int32_t type)
{
    MEM_LOCK;
    mstat[type].alloc--;
    MEM_UNLOCK;
}

/* 打印内存申请释放的debug信息. */
static void _x_mtype_log(char *func, void *memory, const char *file, int32_t line, uint32_t type)
{
    //log_debug(LOG_MEMORY, "%s: %s %p %s %d", func, _x_desc_lookup(type), memory, file, line);
    log_out(LOG_MEMORY, LOG_LVL_DBG,"%s: %s %p %s %d", func, _x_desc_lookup(type), memory, file, line);
}

/* Stats querying from users */
/* Return a pointer to a human friendly string describing
 * the byte count passed in. E.g:
 * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc.
 * Up to 4 significant figures will be given.
 * The pointer returned may be NULL (indicating an error)
 * or point to the given buffer, or point to static storage. */
static const char *_x_mtype_memstr(char *buf, size_t len, uint32_t bytes)
{
    int32_t t = 0;
    int32_t g = 0;
    int32_t m = 0;
    int32_t k = 0;
  
    /* easy cases */
    if (!bytes)
        return "0 bytes";
    if (1 == bytes)
        return "1 byte";

#if 0
    if (sizeof(unsigned long) >= 8)
    /* Hacked to make it not warn on ILP32 machines
     * Shift will always be 40 at runtime. See below too */
        t = bytes >> (sizeof (unsigned long) >= 8 ? 40 : 0);
    else
        t = 0;
#endif
    
    g = bytes >> 30;
    m = bytes >> 20;
    k = bytes >> 10;

    if (t > 10)
    {
        /* The shift will always be 39 at runtime.
         * Just hacked to make it not warn on 'smaller' machines. 
         * Static compiler analysis should mean no extra code */
        if (bytes & (1UL << (sizeof(unsigned long) >= 8 ? 39 : 0)))
            t++;
        snprintf (buf, len, "%4d TiB", t);
    }
    else if (g > 10)
    {
        if (bytes & (1 << 29))
            g++;
        snprintf (buf, len, "%d GiB", g);
    }
    else if (m > 10)
    {
        if (bytes & (1 << 19))
            m++;
        snprintf (buf, len, "%d MiB", m);
    }
    else if (k > 10)
    {
        if (bytes & (1 << 9))
            k++;
        snprintf (buf, len, "%d KiB", k);
    }
    else
        snprintf (buf, len, "%d bytes", bytes);

    return buf;
}

/* 打印内存使用情况根据系统调用. */
static int32_t _x_show_memory_mallinfo(vty_t *vty)
{
    struct mallinfo minfo = mallinfo();
    char buf[MTYPE_MEMSTR_LEN] = {0};

    vty_out(vty, "System allocator statistics:%s", VTY_NEWLINE);
    vty_out(vty, "  Total heap allocated:  %s%s",
           _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.arena), VTY_NEWLINE);
    vty_out(vty, "  Holding block headers: %s%s",
           _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.hblkhd), VTY_NEWLINE);
    vty_out(vty, "  Used small blocks:     %s%s",
           _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.usmblks), VTY_NEWLINE);
    vty_out(vty, "  Used ordinary blocks:  %s%s",
           _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.uordblks), VTY_NEWLINE);
    vty_out(vty, "  Free small blocks:     %s%s",
           _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fsmblks), VTY_NEWLINE);
    vty_out(vty, "  Free ordinary blocks:  %s%s",
           _x_mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fordblks), VTY_NEWLINE);
    vty_out(vty, "  Ordinary blocks:       %ld%s", (unsigned long)minfo.ordblks, VTY_NEWLINE);
    vty_out(vty, "  Small blocks:          %ld%s", (unsigned long)minfo.smblks, VTY_NEWLINE);
    vty_out(vty, "  Holding blocks:        %ld%s", (unsigned long)minfo.hblks, VTY_NEWLINE);
    vty_out(vty, "(see system documentation for 'mallinfo' for meaning)%s", VTY_NEWLINE);
    
    return 1;
}

static void _x_show_separator(vty_t *vty)
{
    vty_out(vty, "-----------------------------%s", VTY_NEWLINE);
}

int32_t _x_show_memory_vty(vty_t *vty, mem_node_t *list)
{
    mem_node_t *m = NULL;
    int32_t needsep = 0;

    for (m = list; m->index >= 0; m++)
    if (0 == m->index)
    {
        if (needsep)
        {
            _x_show_separator(vty);
            needsep = 0;
        }
    }
    else
    {
        vty_out(vty, "%-30s: %10d%s", m->format, mstat[m->index].alloc, VTY_NEWLINE);
        needsep = 1;
    }
    
    return needsep;
}

/* 申请内存，并增加相应的模块的申请数: type -- 模块，size -- 大小 */
static void *_x_malloc (int32_t type, size_t size)
{
    void *memory = NULL;

    memory = malloc(size);

    if (NULL == memory)
        _x_mem_error("malloc", type, size);

    memset(memory, 0, size);

    _x_alloc_inc(type);

    return memory;
}

/* 申请内存，并增加相应的模块的申请数: type -- 模块，size -- 大小 */
static void *_x_malloc_q (int32_t type, size_t size)
{
    void *memory = NULL;

    memory = malloc(size);

    if (NULL == memory)
        _x_mem_error("malloc", type, size);

    _x_alloc_inc(type);

    return memory;
}

/* Allocate memory as in zmalloc, and also clear the memory. */
static void *_x_calloc(int32_t type, size_t size)
{
    void *memory = NULL;

    memory = calloc(1, size);

    if (NULL == memory)
        _x_mem_error("calloc", type, size);

    memset(memory, 0, size);

    _x_alloc_inc(type);

    return memory;
}

/* Given a pointer returned by zmalloc or zcalloc, free it and
 * return a pointer to a new size, basically acting like realloc().
 * Requires: ptr was returned by zmalloc, zcalloc, or zrealloc with the
 * same type.
 * Effects: Returns a pointer to the new memory, or aborts. */
static void *_x_realloc(int32_t type, void *ptr, size_t size)
{
    void *memory = NULL;

    memory = realloc(ptr, size);
    if (NULL == memory)
        _x_mem_error("realloc", type, size);
    
    if (NULL == ptr)
        _x_alloc_inc(type);

    return memory;
}

/* Free memory allocated by z*alloc or zstrdup.
 * Requires: ptr was returned by zmalloc, zcalloc, or zrealloc with the
 * same type.
 * Effects: The memory is freed and may no longer be referenced. */
static void _x_free(int32_t type, void *ptr)
{
    if (ptr != NULL)
    {
        _x_alloc_dec(type);
        free(ptr);
    }
}

/* Duplicate a string, counting memory usage by type.
 * Effects: The string is duplicated, and the return value must
 * eventually be passed to zfree with the same type.  The function will
 * succeed or abort. */
static char *_x_strdup(int32_t type, const char *str)
{
    void *dup = NULL;

    dup = strdup(str);
    if (dup == NULL)
        _x_mem_error("strdup", type, strlen(str));

    _x_alloc_inc(type);

    return dup;
}

/* Interface functions -------------------------------------------------------*/
/* description: 申请内存.
   param:   file    --  调用的文件
            line    --  调用的行数
            type    --  哪个模块调用
            size    --  分配的大小
   return: 分配的内存地址 */
void *mtype_x_malloc(const char *file, int32_t line, int32_t type, size_t size)
{
    void *memory = NULL;

    mstat[type].c_malloc++;
    mstat[type].t_malloc++;

    memory = _x_malloc(type, size);
    _x_mtype_log("x_malloc", memory, file, line, type);

    return memory;
}

void *mtype_x_malloc_q(const char *file, int32_t line, int32_t type, size_t size)
{
    void *memory = NULL;

    mstat[type].c_malloc++;
    mstat[type].t_malloc++;

    memory = _x_malloc_q(type, size);
    _x_mtype_log("x_malloc_q", memory, file, line, type);

    return memory;
}

/* 见mtype_x_malloc. */
void *mtype_x_calloc(const char *file, int32_t line, int32_t type, size_t size)
{
    void *memory = NULL;

    mstat[type].c_calloc++;
    mstat[type].t_calloc++;

    memory = _x_calloc(type, size);
    _x_mtype_log("x_calloc", memory, file, line, type);

    return memory;
}

/* 见mtype_x_malloc. */
void *mtype_x_realloc(const char *file, int32_t line, int32_t type, void *ptr, size_t size)
{
    void *memory = NULL;

    /* Realloc need before allocated pointer. */
    mstat[type].t_realloc++;

    memory = _x_realloc(type, ptr, size);

    _x_mtype_log("x_realloc", memory, file, line, type);

    return memory;
}

/* 见mtype_x_malloc. */
void mtype_x_free(const char *file, int32_t line, int32_t type, void *ptr)
{
    mstat[type].t_free++;

    _x_free(type, ptr);

    _x_mtype_log("x_free", ptr, file, line, type);
}

/* 见mtype_x_malloc. */
char *mtype_x_strdup(const char *file, int32_t line, int32_t type, const char *str)
{
    char *memory = NULL;

    mstat[type].c_strdup++;

    memory = _x_strdup(type, str);

    _x_mtype_log("x_strdup", memory, file, line, type);

    return memory;
}

CMD(show_memory_all,
    show_memory_all_cmd,
    "show memory",
    SHOW_STR
    "Memory statistics\n")
{
    mem_list_t *ml = NULL;
    int needsep = 0;
  
    needsep = _x_show_memory_mallinfo(vty);
  
    for (ml = mlists; ml->nodes; ml++)
    {
        if (needsep)
            _x_show_separator(vty);
        needsep = _x_show_memory_vty(vty, ml->nodes);
    }

    return CMD_SUCCESS;
}

void mem_show(vty_t *vty)
{
    mem_list_t *ml = NULL;
    int needsep = 0;
    
    for (ml = mlists; ml->nodes; ml++)
    {
        if (needsep)
            _x_show_separator(vty);
        needsep = _x_show_memory_vty(vty, ml->nodes);
    }
}

/* description: memory模块初始化.
   param: 
   return: */
void mtype_init(void)
{
    cmd_install_element(COMMON_NODE, &show_memory_all_cmd);
}

/* description: memory模块初始化.
   param: 
   return: */
void mtype_init_befor(void)
{
    /* 初始化线程锁. */
    pthread_mutex_init(&mem_mutex, NULL);
}

/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/
