监控Java cpu、内存自动抓取 jstack 日志脚本-改进版


概述

linux 系统应用性能监控
之前写过一个单文件版linux 监控工具监控Java cpu、内存自动抓取 jstack 日志脚本-升级版,但是感觉用起来不方便,配置都要在源码里面改,不利于部署。
本次项目重新构造项目方式,独立配置文件,新增短信预警通知。


环境

python 2.7 ; centos 7

总体文件

file

配置文件

在这里插入图片描述

核心功能

源码详细注释
片段1

        # 获取cup, mem百分比命令
        # top -p  pid  -n1 : 获取进程 pid 的top 信息,只刷新一次。
        # sed -n '8p'   : 获取第8行
        # awk '{printf $9}' : 获取第9列

        cup_comm = 'top -b -p  ' + pid + '  -n1|sed -n \'8p\'|awk \'{printf $9}\''
        # 内存
        mem_comm = 'top -b -p  ' + pid + '  -n1|sed -n \'8p\'|awk \'{printf $10}\''
        if(cup_comm=='' or mem_comm==''):
            return
        # 取得cpu 占用百分比
        cpu_perc_str = cm.getoutput(cup_comm)
        # 取得内存 占用百分比
        mem_perc_str = cm.getoutput(mem_comm)

片段2

    ps_comm = 'ps aux --sort=-rss|head -10'  # 获取前20行
    os_str = os.popen(ps_comm).read()  # 执行linux命令,并读取执行结果
    arr1 = os_str.split('\n')
    write_time = 'echo ' + time_str_format + ' >> ' + log_dir + 'ps_result.txt'
    os.system(write_time)
    for i in arr1[1:len(arr1)-1]:
        # 1、把多个空格合并成一个空格,并以 , 号分隔
        str1 = re.sub(' +', ',', i)
        # 2、按 , 符进行分割数据
        str2 = str1.split(',')
        # 过滤掉最后一行的空格
        mem_percent = str2[3]
        print '内存的百分比: %s' % mem_percent
        if(float(mem_percent) > 10.0):   # 百分比大于多少执行, 自定义!!!                                      <<------------------!!!!!!!!!

            write_ps = 'echo ' + str1 + ' >> ' + log_dir + 'ps_result.txt'
            senMsg(i)  # 发送短信
            os.system(write_ps)

代码

ConfigInfo.py

# -*- coding: utf-8 -*-

class ConfigInfo():
    def __init__(self):
        with open('monitor.properties', 'r') as dataFile:

            for line in dataFile.readlines():
                # 去掉换行符
                line = line.strip('\n')
                line = line.strip('\r')  # 去掉回车
                # 获取监听端口
                if line.rfind("application.ports", 0, len("application.ports")) != -1:
                    ports_str = (line.split("="))[1]
                    self.ports = [] if ports_str=='' else ports_str.split(",")
                # 监控进程id 信息
                elif line.rfind("process.pids", 0, len("process.pids")) != -1:
                    pids_str = (line.split("="))[1]
                    self.pids = [] if  ports_str=='' else pids_str.split(",")
                # 是否发送短信
                elif line.rfind("short.message.is", 0, len("short.message.is")) != -1:
                    self.sendMsgIs = (line.split("="))[1]
                # 日志存放目录
                elif line.rfind("base.log.dir", 0, len("base.log.dir")) != -1:
                    self.baseLogDir = (line.split("="))[1]
                # 短信发送时间间隔
                elif line.rfind("short.message.interval", 0, len("short.message.interval")) != -1:
                    self.msgInterval = (line.split("="))[1]
                # 发送的电话号码
                elif line.rfind("short.message.phone", 0, len("short.message.phone")) != -1:
                    phones_str = (line.split("="))[1]
                    self.phones = phones_str.split(",")
                # 监控时间频率
                elif line.rfind("monitor.frequency", 0, len("monitor.frequency")) != -1:
                    self.frequency = (line.split("="))[1]
                # 短信地址
                elif line.rfind("short.message.url", 0, len("short.message.url")) != -1:
                    self.sendMsgUrl = (line.split("="))[1]


if __name__ == '__main__':
    c = ConfigInfo()
    print c.ports
    print c.pids
    print c.sendMsgIs
    print c.baseLogDir


Server.py

# -*- coding: utf-8 -*-
import time
import commands as cm
import re
import os
import sys
from ConfigInfo import ConfigInfo
import ShortMsg

lastSendTime = 0  # 上一次发送短信时间
def senMsg(sendTxt):
    if(config.sendMsgIs == 'False'):
        print 'sendMsgIs is false'
        return
    global lastSendTime

    currentTime = int(time.time())
    if(lastSendTime != 0):
        difMin = currentTime - lastSendTime
        if(difMin < int(config.msgInterval)*60):
            print '时间间隔小于短信发送间隔, 短信未发送'
            return
    else:
        ShortMsg.senMsg(sendTxt, config.phones, config.sendMsgUrl)
        lastSendTime = currentTime

# 执行方法
def executeFun(pids):

    pids = list(set(pids))  # 去除重复
    for pid in pids:
        if(pid==''):
            return
        # 获取cup, mem百分比命令
        # top -p  pid  -n1 : 获取进程 pid 的top 信息,只刷新一次。
        # sed -n '8p'   : 获取第8行
        # awk '{printf $9}' : 获取第9列

        cup_comm = 'top -b -p  ' + pid + '  -n1|sed -n \'8p\'|awk \'{printf $9}\''
        # 内存
        mem_comm = 'top -b -p  ' + pid + '  -n1|sed -n \'8p\'|awk \'{printf $10}\''
        if(cup_comm=='' or mem_comm==''):
            return
        # 取得cpu 占用百分比
        cpu_perc_str = cm.getoutput(cup_comm)
        # 取得内存 占用百分比
        mem_perc_str = cm.getoutput(mem_comm)
        if(cpu_perc_str=='' or mem_perc_str==''):
            return
        # 转成数字类型
        cpu_perc = float(cpu_perc_str)
        mem_perc = float(mem_perc_str)

        print cpu_perc
        print mem_perc
        # 获取时间串
        time_str = time.strftime("%Y%m%d%H%M%S", time.localtime())
        print time_str


        pid_exists = len(cm.getoutput('jps |grep ' + pid)) > 0           # pid_exists 是否存在java 进程 id

        if (cpu_perc > 1000.1):  # 百分比大于多少执行, 自定义!!!                                      <<------------------!!!!!!!!!

            if(pid_exists):
                jstack_comm = 'jstack -l ' + pid + ' >> ' + log_dir + 'gc_' + time_str +'_cpu_' + cpu_perc_str + '.log'  # 保存 jstack 日志
            else:
                text = '_cpu_' + cpu_perc_str+'time_'+ time_str
                jstack_comm = 'echo '+text+' >> ' + log_dir +'other_cup.txt'
            os.system(jstack_comm)
            senMsg(jstack_comm)  # 发送短信
        elif (mem_perc > 40.1):  # 百分比大于多少执行, 自定义!!!                                      <<------------------!!!!!!!!!
            if (pid_exists):
                jstack_comm = 'jstack -l ' + pid + ' >> ' + log_dir + 'gc_' + time_str +'_mem_' + mem_perc_str + '.log'   # 保存 jstack 日志
            else:
                text = '_mem_' + mem_perc_str + 'time_' + time_str
                jstack_comm = 'echo ' + text + ' >> ' + log_dir + 'other_mem.txt'
            os.system(jstack_comm)
            senMsg(jstack_comm)  # 发送短信
        # 删除7天前的 log 结尾文件
        rm_log_comm = 'find ' + log_dir + ' -mtime +7 -type f -name "*.log" -exec rm -f {} \;'
        os.system(rm_log_comm)

def getPidsByPorts(ports):
    pidList = []
    for port in ports:
        if(port==''):
            return
        # port = '8090'
        # 获取进程命令 pid_comm ;
        # netstat -ntpl|grep port  : 获取端口号port 的进程信息
        # awk '{printf $7}' : 取第七列
        # cut -d/ -f1  : 以 / 分割后取第一个值
        pid_comm = 'netstat -ntpl|grep ' + port + '|awk \'{printf $7}\'|cut -d/ -f1'
        # 取得进程 pid
        pid = cm.getoutput(pid_comm)
        if (pid == ''):
            print 'this port: %s no pid: ' % port
            continue
        else:
            pidList.append(pid)
    return pidList

def globalMonitor():
    free_comm = 'free'
    os_str = os.popen(free_comm).read()  # 执行linux命令,并读取执行结果
    arr1 = os_str.split('\n')
    write_time = 'echo ' + time_str_format + ' >> ' + log_dir + 'free_result.txt'
    os.system(write_time)
    for i in arr1[:len(arr1) - 1]:
        # 1、把多个空格合并成一个空格,并以 , 号分隔
        str1 = re.sub(' +', ',', i)


        write_free = 'echo ' + str1 + ' >> ' + log_dir + 'free_result.txt'

        os.system(write_free)

    ps_comm = 'ps aux --sort=-rss|head -10'  # 获取前10行
    os_str = os.popen(ps_comm).read()  # 执行linux命令,并读取执行结果
    arr1 = os_str.split('\n')

    write_time = 'echo ' + time_str_format + ' >> ' + log_dir + 'ps_result.txt'
    os.system(write_time)
    for i in arr1[1:len(arr1)-1]:
        # 1、把多个空格合并成一个空格,并以 , 号分隔
        str1 = re.sub(' +', ',', i)
        # 2、按 , 符进行分割数据
        str2 = str1.split(',')
        # 过滤掉最后一行的空格
        ps_top_pid = str2[1]  # pid
        mem_percent = str2[3]   # 内存占用百分比
        ps_top_exedir = str2[10:]  # 执行目录
        print 'PID: %s' % ps_top_pid
        print '内存的百分比: %s' % mem_percent
        print '执行目录: %s' % ps_top_exedir
        print '-------------------------------'
        if(float(mem_percent) > 30.0):   # 百分比大于多少执行, 自定义!!!                                      <<------------------!!!!!!!!!

            write_ps = 'echo ' + str1 + ' >> ' + log_dir + 'ps_result.txt'
            senMsg(i)  # 发送短信
            os.system(write_ps)


if __name__ == '__main__':
    # 获取时间串

    config = ConfigInfo()
    log_dir = config.baseLogDir  # 日志存放目录
    frequency = int(config.frequency)
    while (True):
        time_str_format = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        globalMonitor()
        pids = getPidsByPorts(config.ports)
        pids.extend(config.pids)  # 添加监控进程信息
        executeFun(pids)

        # 延时执行
        time.sleep(frequency)

ShortMsg.py

# -*- coding: utf-8 -*-
import time
import urllib
import urllib2
# 如果配置 short.message 为 True, 则需要修改以下方法内容。

def senMsg(sendTxt, phones, url):

    time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    data = {}
    url = url
    for phone in phones:
        data['mobilelist'] = phone  #
        data['content'] = sendTxt + ' ' + time_str
        params = urllib.urlencode(data)
        response = urllib2.urlopen('?'.join([url, '%s']) % params)
        print 'senMsg response: %s' % response.read()

monitor.properties

# 监控刷新的频率, 单位秒
monitor.frequency=10
# 要监控的进程端口号
application.ports=8085,8086
# 要监控的进程 id 号
process.pids=12345,23562
# 是否发送短信
short.message.is=False
# 发送短信的手机号码
short.message.phone=12345678901,12345678902
# 短信地址
short.message.url=http://localhost:9191/sms/phoneMsg/msmpush
# 上次发送短信和当前发送短信最小时间间隔, 单位分钟
short.message.interval=60
# 日志存放目录
base.log.dir=/hnpwqx/gclogs/app_server/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
jstack生成的Thread Dump日志.docx 系统线程状态 (Native Thread Status) 系统线程有如下状态: deadlock 死锁线程,一般指多个线程调用期间进入了相互资源占用,导致一直等待无法释放的情况。 runnable 一般指该线程正在执行状态中,该线程占用了资源,正在处理某个操作,如通过SQL语句查询数据库、对某个文件进行写入等。 blocked 线程正处于阻塞状态,指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。 waiting on condition 线程正处于等待资源或等待某个条件的发生,具体的原因需要结合下面堆栈信息进行分析。 (1)如果堆栈信息明确是应用代码,则证明该线程正在等待资源,一般是大量读取某种资源且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取,或者正在等待其他线程的执行等。 (2)如果发现有大量的线程都正处于这种状态,并且堆栈信息中得知正等待网络读写,这是因为网络阻塞导致线程无法执行,很有可能是一个网络瓶颈的征兆: 网络非常繁忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写; 网络可能是空闲的,但由于路由或防火墙等原因,导致包无法正常到达; 所以一定要结合系统的一些性能观察工具进行综合分析,比如netstat统计单位时间的发送包的数量,看是否很明显超过了所在网络带宽的限制;观察CPU的利用率,看系统态的CPU时间是否明显大于用户态的CPU时间。这些都指向由于网络带宽所限导致的网络瓶颈。 (3)还有一种常见的情况是该线程在 sleep,等待 sleep 的时间到了,将被唤醒。 waiting for monitor entry 或 in Object.wait() Moniter 是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者class的锁,每个对象都有,也仅有一个 Monitor。 从上图可以看出,每个Monitor在某个时刻只能被一个线程拥有,该线程就是 "Active Thread",而其他线程都是 "Waiting Thread",分别在两个队列 "Entry Set"和"Waint Set"里面等待。其中在 "Entry Set" 中等待的线程状态是 waiting for monitor entry,在 "Wait Set" 中等待的线程状态是 in Object.wait()。 (1)"Entry Set"里面的线程。 我们称被 synchronized 保护起来的代码段为临界区,对应的代码如下: synchronized(obj){} 当一个线程申请进入临界区时,它就进入了 "Entry Set" 队列中,这时候有两种可能性: 该Monitor不被其他线程拥有,"Entry Set"里面也没有其他等待的线程。本线程即成为相应类或者对象的Monitor的Owner,执行临界区里面的代码;此时在Thread Dump中显示线程处于 "Runnable" 状态。 该Monitor被其他线程拥有,本线程在 "Entry Set" 队列中等待。此时在Thread Dump中显示线程处于 "waiting for monity entry" 状态。 临界区的设置是为了保证其内部的代码执行的原子性和完整性,但因为临界区在任何时间只允许线程串行通过,这和我们使用多线程的初衷是相反的。如果在多线程程序中大量使用synchronized,或者不适当的使用它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在Thread Dump中发现这个情况,应该审视源码并对其进行改进。 (2)"Wait Set"里面的线程 当线程获得了Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(通常是被synchronized的对象)的wait()方法,放弃Monitor,进入 "Wait Set"队列。只有当别的线程在该对象上调用了 notify()或者notifyAll()方法,"Wait Set"队列中的线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。"Wait Set"中的线程在Thread Dump中显示的状态为 in Object.wait()。通常来说, 通常来说,当CPU很忙的时候关注 Runnable 状态的线程,反之则关注 waiting for monitor entry 状态的线程。 JVM线程运行状态 (JVM Thread Status)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值