随手记——栈空间使用率实时监测工具

Posted 穿越临界点

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随手记——栈空间使用率实时监测工具相关的知识,希望对你有一定的参考价值。

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。


本篇我们来一起制作查询栈空间利用率的小工具(实际是一个指令)。虽然简单,但是实用,也挺好玩儿的~~

1 缘起

为什么想到做这个呢?

是因为在工作中遇到了2次栈空间不足的问题(随手记——栈空间不足导致的系统异常问题),第一次优化了下变量就解决了,所以没太在意。但第二次有一个新入职不久的同学(年轻人还是很好学的),他问我有没有查询或监测当前栈空间利用率的工具。

我一想人家说的很对啊,有了这样的工具,就可以随时掌握各个线程栈空间利用率情况,不用定位半天业务代码才发现是系统问题。而且对于新增一个线程时栈空间到底开多大合适也有了参考标准。

但是,我到网上搜了下也没有找到合适的工具(大家找到合适的给我说下哈),我想可能是太简单了大佬们不屑去做吧。

于是,我决定自己做一个。

2 需求分析

这样一个小工具应该具备什么样的特性呢?

  • 能够统计线程粒度的栈空间占用率,精确到1%。
  • 以命令行形式呈现,手动输入指令加线程号(例如 kk 1080),系统输出指定线程当前的栈空间占用率。

3 问题分析和解决

3.1 栈空间利用率计算

栈空间利用率的计算原理非常简单,但这也是该工具实现的核心之一。
R a t i o = U s e d T o t a l × 100 % \\mathbf{Ratio} = \\frac{Used}{Total} \\times 100 \\% Ratio=TotalUsed×100%
所以,我们的目标就是从系统中拿到或者计算得到 TotalUsed 就行了。

不啰嗦,直接上代码。代码中 stack_size 对应公式中的 Totalused 对应公式中的 Used

  2 #include <stdio.h>
  3 #include <pthread.h>
  4 #include <string.h>
  5 #include <unistd.h>
  6 #include <signal.h>
  7 #include <stdlib.h>
  9
 10 int monitor_stack(void)
 11 {
 12         size_t esp_val = 0;
 13         pthread_attr_t attr;
 14         void *stack_addr = NULL;
 15         size_t stack_size = 0;
 16         size_t available = 0;
 17         size_t used = 0;
 18         int ratio = 0;
 19
 21         memset(&attr, 0, sizeof(pthread_attr_t));
 22			/* 获取栈地址和栈大小 */
 23         if(pthread_getattr_np(pthread_self(), &attr)) {
 24                 printf("getattr failed.\\n");
 25                 return -1;
 26         }
 27         if(pthread_attr_getstack(&attr, &stack_addr, &stack_size)) {
 28                 printf("getstack failed.\\n");
 29                 return -2;
 30         }
 31         pthread_attr_destroy(&attr);
 32			/* 嵌入汇编获取栈指针的值 */
 33         __asm__ __volatile__("movl %%esp, %0" : "=m"(esp_val) ::); /*x86架构,其他架构需要替换该行代码*/
 34         printf("stack pointer is %#x \\n", esp_val);
 35
 36         available = abs(esp_val - (size_t)stack_addr); /*计算栈可用空间*/
 37         used = stack_size - available; /*计算栈已使用空间*/
 38         ratio = (float)used/(float)stack_size * 100; /*计算栈空间使用率*/
 39			/* 打印输出,当然也可以给到返回值或者出参 */
 40         printf("avail is %d Byte; used is %d Byte;", available, used);
 41         printf("ratio is %d%% \\n", ratio);
 42
 43         return 0;
 44 }

Tips:需要额外说明两点:

  • 获取栈空间大小的接口除了使用pthread库函数外,还可以使用getrlimit接口,或者命令行ulimit -c;获取栈底或栈指针还可以使用局部变量的地址(近似,不精确)。

  • ARM架构中获取栈指针的汇编代码为:size_t sp_val; __asm__ __volatile__ {“mov %0, sp”:"=r"(sp_val):};

3.2 什么时机进行计算

上述统计栈空间使用率的函数是有了,但是什么时候由谁来调用呢?

monitor_stack 统计的是调用时刻的栈空间使用率,所以,该函数的调用点就应该是线程运行过程中的任何时刻,这样才能比较客观的表示线程在运行过程中的栈空间使用率。

怎么样才能做到这点呢?答案是使用系统的 异步机制 —— 信号

这里,给出一个简单的例子(简化部分错误处理)。

 45
 46 void sighandler(int sig) /*信号处理函数*/
 47 {
 48
 49         if (SIGRTMAX == sig)
 50                 (void)monitor_stack();/*进行栈空间使用率统计*/
 51
 52 }
 53 #define NUM (1024 / 100 * 55) /*通过调整该值大小来调整栈空间使用率*/
 54
 55 void *thread1(void *args) /*目标线程函数体*/
 56 {
 57         char c[8192*NUM]; /*通过控制该变量大小来调试栈空间使用率*/
 58         for (;;) {
 59                 printf("thread1 alive.\\n");
 60                 sleep(100);
 61         }
 62 }
 63
 64 int kk(pthread_t tid) /*负责给目标线程发送触发信号*/
 65 {
 66         if (pthread_kill(tid, SIGRTMAX)) {
 67                 printf("signal sent to thread1 failed.\\n");
 68                 return -1;
 69         }
 70         return 0;
 71 }
 72
 73 int main(int argc, char *argv[])
 74 {
 75         char a = 0;
 76         pthread_t tid1;
 78
 79         /* 安装信号处理函数;这里使用的是SIGRTMAX信号,你可以选择自己喜欢的其他信号 */
 80         if (SIG_ERR == signal(SIGRTMAX, &sighandler)) {
 81                 perror("");
 82                 exit(-1);
 83         }
 84         if (pthread_create(&tid1, NULL, thread1, NULL)) {/*创建一个线程(这就是我们统计栈空间的目标线程)*/
 85                 printf("create thread1 failed.\\n");
 86                 exit(-2);
 87         }
 88         sleep(1); /*改行代码的目的是等待我们创建的线程运行稳定;否则第一次统计的栈空间占用率不准确*/
 89
 90         for (;;) {
 91                 printf("main alive.\\n");
 92                 if (kk(tid1)) /*调用kk指令*/
 93                         exit(-3);
 94                 sleep(1); /*发送周期为1s*/
 95         }
 96
 97         return 0;
 98 }

4 落成

上述代码示例的输出结果如下:

thread1 alive.
main alive.
stack pointer is 0xe97c26e0
avail is 3880672 Byte; used is 4512032 Byte;ratio is 53%
    
thread1 alive.
main alive.
stack pointer is 0xe97c26e0
avail is 3880672 Byte; used is 4512032 Byte;ratio is 53%
...

运行程序后,每隔一秒会统计一次指定线程的栈使用率。由于线程中的代码量很小,所以栈使用率基本保持不变。

到这里,距离我们的需求其实还有一小步没走,就是如何将 kk 指令加到shell指令系统中去,这个有机会我再另起一篇文章进行说明。


5 改进

我们实现的只是单个时间点、单个线程的统计。基于此,可以使用定时器、或者专门创建一个栈空间使用率的线程来统计更长时间范围内的各个线程的栈空间使用率。还可以和告警以及日志系统结合起来进行结果的呈现。

我只是抛砖引玉一下,大家可以根据自己的项目需求进行不断地完善。


恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

以上是关于随手记——栈空间使用率实时监测工具的主要内容,如果未能解决你的问题,请参考以下文章

随手记——栈空间不足导致的系统异常问题

随手记——栈空间不足导致的系统异常问题

随手记——Linux中编写实时性代码时需要注意哪些问题

随手记——Linux中编写实时性代码时需要注意哪些问题

nat模块随手记

随手记3:C#Unity中随机数的使用