首页 > 系统相关 >PFS内存统计信息的聚合与准确性问题(四)

PFS内存统计信息的聚合与准确性问题(四)

时间:2023-08-02 17:59:21浏览次数:45  
标签:count stat capacity free 准确性 PFS 内存 memory size

内存统计信息的聚合

内存统计信息的聚合总共有5个维度,也分别对应以下5张表,分别是:

  • MEMORY_SUMMARY_BY_ACCOUNT_BY_EVENT_NAME
  • MEMORY_SUMMARY_BY_HOST_BY_EVENT_NAME
  • MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
  • MEMORY_SUMMARY_BY_USER_BY_EVENT_NAME
  • MEMORY_SUMMARY_GLOBAL_BY_EVENT_NAME

其包括内存分配、释放、聚合的涉及的入口函数及架构如下:

可以看到最基础的维度为thread,然后再往父bucket中进行聚合。

聚合的“主要”逻辑堆栈如下:

|PSI_THREAD_CALL(delete_current_thread)
|--pfs_delete_current_thread_vc
|----aggregate_thread
|------aggregate_thread_memory
|--------aggregate_all_memory_with_reassign
|----------memory_full_aggregate_with_reassign
|--------aggregate_all_memory
|----------memory_full_aggregate

在线程退出时,会调用PSI_THREAD_CALL(delete_current_thread)接口进行该连接相关的内存统计信息的聚合,并释放pfs thread对象。

其核心逻辑函数为aggregate_thread_memory,代码如下:

void aggregate_thread_memory(bool alive, PFS_thread *thread,
                             PFS_account *safe_account, PFS_user *safe_user,
                             PFS_host *safe_host) {
  if (thread->read_instr_class_memory_stats() == nullptr) {
    return;
  }

  if (likely(safe_account != nullptr)) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
      to MEMORY_SUMMARY_BY_ACCOUNT_BY_EVENT_NAME.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_account->write_instr_class_memory_stats(),
        global_instr_class_memory_array);

    return;
  }

  if ((safe_user != nullptr) && (safe_host != nullptr)) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME to:
      -  MEMORY_SUMMARY_BY_USER_BY_EVENT_NAME
      -  MEMORY_SUMMARY_BY_HOST_BY_EVENT_NAME
      in parallel.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_user->write_instr_class_memory_stats(),
        safe_host->write_instr_class_memory_stats(),
        global_instr_class_memory_array);
    return;
  }

  if (safe_user != nullptr) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME to:
      -  MEMORY_SUMMARY_BY_USER_BY_EVENT_NAME
      -  MEMORY_SUMMARY_GLOBAL_BY_EVENT_NAME
      in parallel.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_user->write_instr_class_memory_stats(),
        global_instr_class_memory_array, global_instr_class_memory_array);
    return;
  }

  if (safe_host != nullptr) {
    /*
      Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
      to MEMORY_SUMMARY_BY_HOST_BY_EVENT_NAME, directly.
    */
    aggregate_all_memory_with_reassign(
        alive, thread->write_instr_class_memory_stats(),
        safe_host->write_instr_class_memory_stats(),
        global_instr_class_memory_array);
    return;
  }
  /*
    Aggregate MEMORY_SUMMARY_BY_THREAD_BY_EVENT_NAME
    to MEMORY_SUMMARY_GLOBAL_BY_EVENT_NAME.
  */
  aggregate_all_memory(alive, thread->write_instr_class_memory_stats(),
                       global_instr_class_memory_array);
}

根据代码可知,thread维度最为基础维度,通常情况下用户线程的内存统计信息都会从thread维度汇聚到account维度;MySQL后台线程或插件等会被聚合到global维度。所以这里我们主要关注account和global维度的具体聚合逻辑。

account维度的聚合实现主要通过aggregate_all_memory_with_reassign(bool alive,

                                        PFS_memory_safe_stat *from_array,

                                        PFS_memory_shared_stat *to_array,

                                        PFS_memory_shared_stat *global_array)函数实现,其中alive总是为false,from_array为thread维度的instruments内存统计信息数组指针,to_array为account维度的instruments内存统计信息数据指针,global_array为global维度的instruments内存统计信息数组指针。具体实现代码如下:

void aggregate_all_memory_with_reassign(bool alive,
                                        PFS_memory_safe_stat *from_array,
                                        PFS_memory_shared_stat *to_array,
                                        PFS_memory_shared_stat *global_array) {
  PFS_memory_safe_stat *from;
  PFS_memory_safe_stat *from_last;
  PFS_memory_shared_stat *to;

  from = from_array;
  from_last = from_array + memory_class_max;
  to = to_array;

  if (alive) {
    for (; from < from_last; from++, to++) {
      memory_partial_aggregate(from, to);
    }
  } else {
    PFS_memory_shared_stat *global;
    global = global_array;
    for (; from < from_last; from++, to++, global++) {
      memory_full_aggregate_with_reassign(from, to, global);
      from->reset();
    }
  }
}

在for循环中依次迭代from_array中的instrument内存统计信息往父bucket中进行聚合,其聚合实现代码如下:

void memory_full_aggregate_with_reassign(const PFS_memory_safe_stat *from,
                                         PFS_memory_shared_stat *stat,
                                         PFS_memory_shared_stat *global) {
  if (!from->m_used) {
    return;
  }

  stat->m_used = true;

  size_t alloc_count = from->m_alloc_count;
  size_t free_count = from->m_free_count;
  size_t alloc_size = from->m_alloc_size;
  size_t free_size = from->m_free_size;

  size_t net;
  size_t capacity;

  if (likely(alloc_count <= free_count)) {  
    /* Nominal path */
    stat->m_alloc_count += alloc_count;
    stat->m_free_count += free_count;
    stat->m_alloc_count_capacity += from->m_alloc_count_capacity;
    stat->m_free_count_capacity += from->m_free_count_capacity;
  } else {      
    global->m_used = true;

    stat->m_alloc_count += free_count; /* base */
    stat->m_free_count += free_count;

    /* Net memory contributed affected to the global bucket directly. */
    net = alloc_count - free_count;
    global->m_alloc_count += net;

    stat->m_alloc_count_capacity += from->m_alloc_count_capacity;

    size_t free_count_capacity;
    free_count_capacity = from->m_free_count_capacity;
    capacity = std::min(free_count_capacity, net);
    free_count_capacity -= capacity;

    /*
      Corresponding low watermark split between the parent and global
      bucket.
    */
    stat->m_free_count_capacity += free_count_capacity;
    global->m_free_count_capacity += capacity;
  }

  if (likely(alloc_size <= free_size)) {
    /* Nominal path. */
    stat->m_alloc_size += alloc_size;
    stat->m_free_size += free_size;
    stat->m_alloc_size_capacity += from->m_alloc_size_capacity;
    stat->m_free_size_capacity += from->m_free_size_capacity;
  } else {
    /* Global net alloc. */
    global->m_used = true;

    stat->m_alloc_size += free_size; /* base */
    stat->m_free_size += free_size;

    net = alloc_size - free_size;
    global->m_alloc_size += net;

    stat->m_alloc_size_capacity += from->m_alloc_size_capacity;

    size_t free_size_capacity;
    free_size_capacity = from->m_free_size_capacity;
    capacity = std::min(free_size_capacity, net);
    free_size_capacity -= capacity;

    stat->m_free_size_capacity += free_size_capacity;
    global->m_free_size_capacity += capacity;
  }
}

通过代码可知,通常情况下是直接将thread维度的统计信息属性值与account维度的统计信息属性值进行相加来进行聚合,即(alloc_count <= free_count)。

但还有一种情况,即父线程调用子线程进行一些工作,通常有两种场景,比如1.event调度,调度线程调用worker线程进行工作 2. main线程接受用户连接进行工作。这两种情况会产生内存分配与释放在同一线程的不均衡,比如内存的分配是在X线程进行的,但是内存的释放却是在Y线程进行的。这里的情况就对应函数中的另一半逻辑,即(alloc_count > free_count),此时可以理解为一个用户连接分配了一些global类型的内存(内存claim),在用户连接退出时,会将global类型的这部分内存直接聚合到global级别。

通过以上的逻辑分析,我们可以得出一个结论:内存统计信息是从thread维度往父bucket中进行聚合,其中通常会聚合到account维度和global维度,而在进行account维度的聚合时,通常情况下就是将thread维度的统计信息属性值直接与account维度的统计信息属性值相加,如果存在内存claim的情况,则会将额外分配的global类型的内存聚合到global级别。

从thread维度直接聚合到global维度的主要实现函数为memory_full_aggregate,具体实现代码如下:

void memory_full_aggregate(const PFS_memory_safe_stat *from,
                           PFS_memory_shared_stat *stat) {
  if (!from->m_used) {
    return;
  }
  stat->m_used = true;

  stat->m_alloc_count += from->m_alloc_count;
  stat->m_free_count += from->m_free_count;
  stat->m_alloc_size += from->m_alloc_size;
  stat->m_free_size += from->m_free_size;

  stat->m_alloc_count_capacity += from->m_alloc_count_capacity;
  stat->m_free_count_capacity += from->m_free_count_capacity;
  stat->m_alloc_size_capacity += from->m_alloc_size_capacity;
  stat->m_free_size_capacity += from->m_free_size_capacity;
}

这一部分的代码相对比较简单,直接就是将thread维度的统计信息属性值与global维度的统计信息属性值相加即可。

统计信息与OS内存使用不一致的问题

现象1:统计信息大于OS使用量

当前实例使用内存为1.4G

Buffer Pool设置为1G

相关内存统计信息为1G

此时将Buffer Pool调整为2G

如果此时分配的内存都被使用了,那么在OS上看到的实例使用的内存应该至少是大于2G的,但此时实际使用量却是1.6G。

此时产生的现象就是在OS层面观测到的实例实际使用的内容量与MySQL内存统计信息里看到的不一致。

问题产生的原因主要是Linux内存主要是通过虚拟内存文件映射的方式来管理的,当MySQL向Linux申请内存时,分配的都是虚拟内存,并没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,会发生缺页中断,操作系统再进行物理内存的分配,然后建立虚拟内存和物理内存之间的映射关系。

现象2:内存统计信息出现负值

在上一章节关于内存统计信息聚合的介绍中,我们看到在memory_full_aggregate_with_reassign函数中,内存聚合有两个路径,其中一个是(likely(alloc_size <= free_size)),从这个条件中我们可以看到alloc_size是有可能会小于free_size的,在PS表中,CURRENT_NUMBER_OF_BYTES_USED的值是通过alloc_size-free-size计算出来的,所以CURRENT_NUMBER_OF_BYTES_USED的值就也可能会出现负值。

而出现负值显然是不合理的,此种情况就属于是Bug缺陷了。在MariaDB的官方jira中,也找到了相关类似的issue:

https://jira.mariadb.org/browse/MDEV-23936,不过看记录已经过去三年了,官方也没有给出相关解释或解决方案。

还有一点,由于sys.memory_global_total表是performance_schema.memory_summary_global_by_event_name的视图,而total_allocated是通过sum(CURRENT_NUMBER_OF_BYTES_USED)得到的,那么既然CURRENT_NUMBER_OF_BYTES_USED的值是有可能会出现负值,所有total_allocated这个总计值就也不可信了。还有一点,我们之前讲过,内存统计信息的聚合是有几个不同的维度的,并不是所有的统计信息最后都会被聚合到Global级别,所有将sys.memory_global_total的total_allocated值作为内存分配量的统计值是一点不准确的。

现象3:存在未被统计的内存操作

在MySQL相关代码中,明确定义了只有这些文件中的内存分配会被PFS监控,这里这列出了一部分的MySQL代码文件,所以一定存在未被统计的内存。

/** List of filenames that allocate memory and are instrumented via PFS. */
static constexpr const char *auto_event_names[] = {
    /* Keep this list alphabetically sorted. */
    "api0api",
    "api0misc",
    "btr0btr",
    "btr0bulk",
    "btr0cur",
    "btr0pcur",
    "btr0sea",
    "btr0types",
    "buf",
    "buf0buddy",
    "buf0buf",
    "buf0checksum",
    "buf0dblwr",
    "buf0dump",
    "buf0flu",
    "buf0lru",
    "buf0rea",
    "buf0stats",
    "buf0types",
    "checksum",
    "crc32",
    "create",
    "data0data",
    "data0type",
    "data0types",
    "db0err",
    "dict",
    "dict0boot",
    "dict0crea",
    "dict0dd",
    "dict0dict",
    "dict0load",
    "dict0mem",
    "dict0priv",
    "dict0sdi",
    "dict0stats",
    "dict0stats_bg",
    "dict0types",
    "dyn0buf",
    "dyn0types",
    "eval0eval",
    "eval0proc",
    "fil0fil",
    "fil0types",
    "file",
    "fsp0file",
    "fsp0fsp",
    "fsp0space",
    "fsp0sysspace",
    "fsp0types",
    "fts0ast",
    "fts0blex",
    "fts0config",
    "fts0fts",
    "fts0opt",
    "fts0pars",
    "fts0plugin",
    "fts0priv",
    "fts0que",
    "fts0sql",
    "fts0tlex",
    "fts0tokenize",
    "fts0types",
    "fts0vlc",
    "fut0fut",
    "fut0lst",
    "gis0geo",
    "gis0rtree",
    "gis0sea",
    "gis0type",
    "ha0ha",
    "ha0storage",
    "ha_innodb",
    "ha_innopart",
    "ha_prototypes",
    "handler0alter",
    "hash0hash",
    "i_s",
    "ib0mutex",
    "ibuf0ibuf",
    "ibuf0types",
    "lexyy",
    "lob0lob",
    "lock0iter",
    "lock0lock",
    "lock0prdt",
    "lock0priv",
    "lock0types",
    "lock0wait",
    "log0log",
    "log0recv",
    "log0write",
    "mach0data",
    "mem",
    "mem0mem",
    "memory",
    "mtr0log",
    "mtr0mtr",
    "mtr0types",
    "os0atomic",
    "os0event",
    "os0file",
    "os0numa",
    "os0once",
    "os0proc",
    "os0thread",
    "page",
    "page0cur",
    "page0page",
    "page0size",
    "page0types",
    "page0zip",
    "pars0grm",
    "pars0lex",
    "pars0opt",
    "pars0pars",
    "pars0sym",
    "pars0types",
    "que0que",
    "que0types",
    "read0read",
    "read0types",
    "rec",
    "rem0cmp",
    "rem0rec",
    "rem0types",
    "row0ext",
    "row0ftsort",
    "row0import",
    "row0ins",
    "row0log",
    "row0merge",
    "row0mysql",
    "row0purge",
    "row0quiesce",
    "row0row",
    "row0sel",
    "row0types",
    "row0uins",
    "row0umod",
    "row0undo",
    "row0upd",
    "row0vers",
    "sess0sess",
    "srv0conc",
    "srv0mon",
    "srv0srv",
    "srv0start",
    "srv0tmp",
    "sync0arr",
    "sync0debug",
    "sync0policy",
    "sync0sharded_rw",
    "sync0rw",
    "sync0sync",
    "sync0types",
    "trx0i_s",
    "trx0purge",
    "trx0rec",
    "trx0roll",
    "trx0rseg",
    "trx0sys",
    "trx0trx",
    "trx0types",
    "trx0undo",
    "trx0xa",
    "usr0sess",
    "usr0types",
    "ut",
    "ut0byte",
    "ut0counter",
    "ut0crc32",
    "ut0dbg",
    "ut0link_buf",
    "ut0list",
    "ut0lock_free_hash",
    "ut0lst",
    "ut0mem",
    "ut0mutex",
    "ut0new",
    "ut0pool",
    "ut0rbt",
    "ut0rnd",
    "ut0sort",
    "ut0stage",
    "ut0ut",
    "ut0vec",
    "ut0wqueue",
    "zipdecompress",
};

我们找一个未被统计的案例,可以看到,在下图的堆栈中,在内存跟踪接口函数pfs_memory_alloc_vc中,在尝试进行内存分配跟踪时,由于找不到相关的Performance Schema Key,则函数直接return,不再进行后续的内存分配跟踪。(通过堆栈可以看到,该内存分配相关动作为后台master线程在进行主循环):

  TRANSLATE with x English
Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian  
  TRANSLATE with COPY THE URL BELOW Back EMBED THE SNIPPET BELOW IN YOUR SITE Enable collaborative features and customize widget: Bing Webmaster Portal Back     此页面的语言为英语   翻译为中文(简体)        
  • 中文(简体)
  • 中文(繁体)
  • 丹麦语
  • 乌克兰语
  • 乌尔都语
  • 亚美尼亚语
  • 俄语
  • 保加利亚语
  • 克罗地亚语
  • 冰岛语
  • 加泰罗尼亚语
  • 匈牙利语
  • 卡纳达语
  • 印地语
  • 印尼语
  • 古吉拉特语
  • 哈萨克语
  • 土耳其语
  • 威尔士语
  • 孟加拉语
  • 尼泊尔语
  • 布尔语(南非荷兰语)
  • 希伯来语
  • 希腊语
  • 库尔德语
  • 德语
  • 意大利语
  • 拉脱维亚语
  • 挪威语
  • 捷克语
  • 斯洛伐克语
  • 斯洛文尼亚语
  • 旁遮普语
  • 日语
  • 普什图语
  • 毛利语
  • 法语
  • 波兰语
  • 波斯语
  • 泰卢固语
  • 泰米尔语
  • 泰语
  • 海地克里奥尔语
  • 爱沙尼亚语
  • 瑞典语
  • 立陶宛语
  • 缅甸语
  • 罗马尼亚语
  • 老挝语
  • 芬兰语
  • 英语
  • 荷兰语
  • 萨摩亚语
  • 葡萄牙语
  • 西班牙语
  • 越南语
  • 阿塞拜疆语
  • 阿姆哈拉语
  • 阿尔巴尼亚语
  • 阿拉伯语
  • 韩语
  • 马尔加什语
  • 马拉地语
  • 马拉雅拉姆语
  • 马来语
  • 马耳他语
  • 高棉语
 

标签:count,stat,capacity,free,准确性,PFS,内存,memory,size
From: https://www.cnblogs.com/wagaga/p/17601354.html

相关文章

  • MySQL内存分配详解
    InnoDB存储引擎层基础内存分配如果在编译MySQL的时候不开启FPS的监控,InnoDB对动态内存(heap)的分配和释放使用基础的new、delete、malloc、free等。默认InnoDB对内存的分配和回收会添加FPS的监控模块。InnoDB对动态内存(heap)的分配和回收使用封装后的函数,主要在ut_allocator类中实......
  • jdk7 jdk8 堆内存区别
    1、堆内存划分在JDK7以及其前期的JDK版本中,堆内存通常被分为三块区域Nursery内存(young generation)、长时内存(oldgeneration)、永久内存(PermanentGenerationfor VMMatedata),显示如下图: jdk7之前堆内存不够最常见的错误就是OOM(OutOfMemoryError)栈内存溢出最常......
  • Spring内存码
    Spring内存码依然不会配环境orz,干脆直接拿以前那个java-sec-code了,springboot版本2.1.5.RELEASEspring内存码基础的有controller型和interceptor型,两个组件都可以动态添加,注入思路和以前一样,所以先看初始化的流程一、Controller型controller作用是接收特定参数,与@RequestMappi......
  • win 11 无法安装ensp 组件VBox(版本过老)导致AR路由器报错 40,关闭win11 内存完整性 开关
        解决办法如下:1、先关闭内存完整性 2、重新安装vbox(成功) 3、启动ensp(无40报错) ......
  • SQL Server 内存占用较高 - 清除缓存 或 设置内存最大占用值
    SQLServer对服务器内存的使用策略是用多少内存就占用多少内存,只用在服务器内存不足时,才会释放一点占用的内存,所以SQLServer服务器内存往往会占用很高查看内存状态:DBCCMemoryStatus这些内存一般都是SqlServer运行时候用作缓存的:数据缓存:执行查询语句,SqlServer会将相......
  • 【八股文 02】C++ 进程内存布局及其相关知识
    1引言本文环境为Linux操作系统(x86)+C++。目的是了解进程内存布局,但是在了解的过程中发现需要前置一些知识,因此内容概览如下所示:1C/C++程序从源代码到可执行程序的构建过程1.1预处理,也叫预编译1.2编译1.3汇编1.4链接2各平台文件格式3ELF文件3.1ELF文......
  • C++内存管理基础
    在c语言中内存管理函数为malloc和free,而在c++中内存管理的函数则是new和delete。首先来看new和delete对于申请的内置类型的空间是如何处理的内置类型的处理申请连续的多个空间voidtest1(){ int*ret=(int*)malloc(sizeof(int)*10); int*rett=newint[10];//和malloc一......
  • 初学C语言day07--指针与堆内存
    什么是指针:指针是一种特殊的数据类型,使用它可以定义指针变量,指针变量中存储的是整形数据,该整型数据代表了内存的编号(地址),可以通过这个编号访问对应的内存为什么要使用指针:1、函数之间是相互独立的,但是有时候需要共享变量传参是单向值传递全局变量可以共享,但是容易命名冲突......
  • 使用VS开发人员工具观察类在内存中的布局
    1.先要生成相应文件2.打开VS2019开发人员工具3.cd至文件目录4.输入cl/d1reportSingleClassLayoutanimaldemo.cpp其中reportSingleClassLayoutanimalLayout后面的animal为我们想要查看的类名,demo.cpp是其所在的源文件......
  • Java的内存泄漏
    Java的内存泄漏java的一个重要优点就是通过垃圾收集器(GarbageCollection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也......