设为首页 收藏本站
查看: 1654|回复: 0

[经验分享] 利用Zabbix低级发现结合jstat命令自动监控Java进程

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-11-2 10:35:33 | 显示全部楼层 |阅读模式
一 应用场景描述
最近线上业务的Tomcat总是崩溃停止服务。使用jstat命令查看Java进程的JVM内存信息。但是使用命令jstat只能查看一段时间的数据,不能总是盯着屏幕手动查看,于是想到了将jstat的数据通过zabbix进行绘图展现并报警。同时,一台服务器上可能会有多个Java程序在运行,包括不同的Tomcat,或者是其他Java应用,例如Logstash,Elasticsearch等。这就需要用到Zabbix的Low Level Discovery功能。

二 编写监控脚本
在编写脚本之前需要了解jstat命令的使用,详细使用方法可以参考官方文档或者man手册。还有需要了解JVM虚拟机的内存管理和垃圾回收机制。知道什么是Eden Space,S0 Space,S1 Space和Old Space以及Perm Space等
Java-Memory-Model-450x186.jpg
关于Java内存管理和垃圾回收的理论知识可以参考文章

http://www.journaldev.com/2856/j ... n-monitoring-tuning

1
2
3
$ sudo /opt/app/jdk/bin/jstat -gc 30166
S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
195904.0 195904.0 19609.5  0.0   1567680.0 561577.4 13769152.0 2668191.5  42944.0 42827.3   8714  345.808   0      0.000  345.808




1
2
3
$ sudo /opt/app/jdk/bin/jstat -gcutil 30166
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
10.01   0.00  90.27  19.38  99.73   8714  345.808     0    0.000  345.808



1
2
3
$ sudo /opt/app/jdk/bin/jstat -gccapacity 30166
NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC      PGCMN    PGCMX     PGC       PC     YGC    FGC
1959488.0 1959488.0 1959488.0 195904.0 195904.0 1567680.0 13769152.0 13769152.0 13769152.0 13769152.0  21248.0 169984.0  42944.0  42944.0   8717     0




-gc 和 -gccapacity显示的大小是KB
显示两行信息
如果通过zabbix agent的方式获取不同字段的值
可以这样
sudo /opt/app/jdk/bin/jstat -gcutil 30166|awk '{print $4}'|grep -E '^[0-9]'
如果使用zabbix sender的方式发送数据,可以将显示的结果生成一个python字典

{'S0':7.43,'S1':0.00,'E':86.62',.................}
将这个字典中的数据全部通过zabbix_sender发送到zabbix_proxy或者zabbix_server,需要注意的是zabbix_sender指定的主机名需要和zabbix页面配置的主机名相同

Java服务发现脚本
java_discovery.py

脚本中的发现规则根据情况自己定义,这里定义三个规则,/opt/app/下面所有tomcat-开头的tomcat服务,/opt/logs/logstash/和/opt/app/elasticsearch目录下的规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#/usr/bin/python
#This script is used to discovery disk on the server
import subprocess
import os
import socket
import json
import glob

java_names_file='java_names.txt'
javas=[]


if os.path.isfile(java_names_file):
#   print 'java_names_file exists!'
#####
##### here should use % (java_names_file) instead of using the python variable java_names_file directly inside the '''   ''' quotes
#####

   args='''awk -F':' '{print $1':'$2}' %s'''  % (java_names_file)
   t=subprocess.Popen(args,shell=True,stdout=subprocess.PIPE).communicate()[0]
elif glob.glob('/opt/app/tomcat-*') and not os.path.isdir('/opt/logs/logstash') and not os.path.isdir('/opt/app/elasticsearch/config'):
   t=subprocess.Popen('ls /opt/app/tomcat-*|grep tomcat_',shell=True,stdout=subprocess.PIPE)

elif not glob.glob('/opt/app/tomcat-*') and os.path.isdir('/opt/logs/logstash') and not os.path.isdir('/opt/app/elasticsearch/config'):
   t=subprocess.Popen(''' ls /opt/logs/logstash/|grep -E 'logstash_'|sed 's/.log//g' ''',shell=True,stdout=subprocess.PIPE)

elif not glob.glob('/opt/app/tomcat-*') and not os.path.isdir('/opt/logs/logstash') and  os.path.isdir('/opt/app/elasticsearch/config'):
   t=subprocess.Popen(''' ls /opt/app/elasticsearch/config|grep -E 'elasticsearch.yml'| sed 's/.yml//g' ''',shell=True,stdout=subprocess.PIPE)

elif not glob.glob('/opt/app/tomcat-*') and os.path.isdir('/opt/logs/logstash') and  os.path.isdir('/opt/app/elasticsearch/config'):
   t=subprocess.Popen(''' ls /opt/logs/logstash  /opt/app/elasticsearch/config|grep -E 'logstash_|elasticsearch.yml'| sed 's/.log\|.yml//g' ''',shell=True,stdout=subprocess.PIPE)

elif  glob.glob('/opt/app/tomcat-*')  and os.path.isdir('/opt/logs/logstash') and not os.path.isdir('/opt/app/elasticsearch/config'):
   t=subprocess.Popen('''ls /opt/app/tomcat-* /opt/logs/logstash/|grep -E 'tomcat_|logstash_'|sed 's/.log//g' ''',shell=True,stdout=subprocess.PIPE)

elif  glob.glob('/opt/app/tomcat-*') and os.path.isdir('/opt/logs/logstash')  and os.path.isdir('/opt/app/elasticsearch/config'):
      
   t=subprocess.Popen('''ls  /opt/app/tomcat-* /opt/logs/logstash/ /opt/app/elasticsearch/config|grep -E 'tomcat_|logstash_|elasticsearch.yml'| sed 's/.log\|.yml//g' ''',shell=True,stdout=subprocess.PIPE)


for java in t.stdout.readlines():
    if len(java) != 0:
       javas.append({'{#JAVA_NAME}':java.strip('\n')})
print json.dumps({'data':javas},indent=4,separators=(',',':'))




执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ python java_discovery.py
{
    "data":[
        {
            "{#JAVA_NAME}":"elasticsearch"
        },
        {
            "{#JAVA_NAME}":"logstash_central"
        },
        {
            "{#JAVA_NAME}":"logstash_shipper"
        }
    ]
}





在模板中的items,graphs,triggers中使用{#JAVA_NAME}这个宏变量

jstat状态信息获取脚本
jstat_status.py
脚本根据https://github.com/gcharot/Zabbix/tree/master/zjstat 这里的代码进行改编

脚本中主要做以下事情:
找到指定Java服务的pid,然后执行jstat命令,然后执行zabbix_sender命令


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/python

import subprocess
import sys
import os

__maintainer__ = "John Wang"

jps = '/opt/app/jdk/bin/jps'
jstat = '/opt/app/jdk/bin/jstat'

zabbix_sender = "/opt/app/zabbix/sbin/zabbix_sender"
zabbix_conf = "/opt/app/zabbix/conf/zabbix_agentd.conf"      
send_to_zabbix = 1

#"{#JAVA_NAME}":"tomcat_web_1"


def usage():
    """Display program usage"""

    print "\nUsage : ", sys.argv[0], " java_name alive|all"
    print "Modes : \n\talive : Return pid of running processs\n\tall : Send jstat stats as well"
    sys.exit(1)


class Jprocess:

    def __init__(self, arg):
        self.pdict = {
        "jpname": arg,
        }

        self.zdict = {
        "Heap_used" : 0,
                "Heap_ratio" : 0,
        "Heap_max" : 0,
        "Perm_used" : 0,
                "Perm_ratio" : 0,
        "Perm_max"  : 0,
                "S0_used"   : 0,
                "S0_ratio"  : 0,
                "S0_max"    : 0,
                "S1_used"   : 0,
                "S1_ratio"  : 0,
                "S1_max"    : 0,
                "Eden_used" : 0,
                "Eden_ratio" : 0,
                "Eden_max"  : 0,
                "Old_used"  : 0,
                "Old_ratio" : 0,
                "Old_max"   : 0,
                "YGC"       : 0,
                "YGCT"      : 0,
                "YGCT_avg"      : 0,
                "FGC"       : 0,
                "FGCT"      : 0,
                "FGCT_avg"      : 0,
                "GCT"       : 0,
                "GCT_avg"       : 0,
                 
        }


    def chk_proc(self):
#  ps -ef|grep java|grep tomcat_web_1|awk '{print $2}'
#                print self.pdict['jpname']
                pidarg = '''ps -ef|grep java|grep %s|grep -v grep|awk '{print $2}' ''' %(self.pdict['jpname'])
        pidout = subprocess.Popen(pidarg,shell=True,stdout=subprocess.PIPE)
                pid = pidout.stdout.readline().strip('\n')
                if pid != "" :
                   self.pdict['pid'] = pid
#                   print "Process found :", java_name, "with pid :", self.pdict['pid']
                else:
                   self.pdict['pid'] = ""
#                   print "Process not found"
                return self.pdict['pid']

    def get_jstats(self):
        if self.pdict['pid'] == "":
            return False
        self.pdict.update(self.fill_jstats("-gc"))
        self.pdict.update(self.fill_jstats("-gccapacity"))
        self.pdict.update(self.fill_jstats("-gcutil"))

#       print "\nDumping collected stat dictionary\n-----\n", self.pdict, "\n-----\n"

    def fill_jstats(self, opts):
#       print "\nGetting", opts, "stats for process", self.pdict['pid'], "with command : sudo", jstat, opts, self.pdict['pid'] ,"\n"
        jstatout = subprocess.Popen(['sudo', jstat, opts, self.pdict['pid']], stdout=subprocess.PIPE)
        stdout, stderr = jstatout.communicate()
        legend, data = stdout.split('\n',1)
            mydict = dict(zip(legend.split(), data.split()))
        return mydict

    def compute_jstats(self):
        if self.pdict['pid'] == "":
            return False
                self.zdict['S0_used'] = format(float(self.pdict['S0U']) * 1024,'0.2f')
                self.zdict['S0_max'] =  format(float(self.pdict['S0C']) * 1024,'0.2f')
                self.zdict['S0_ratio'] = format(float(self.pdict['S0']),'0.2f')

                self.zdict['S1_used'] = format(float(self.pdict['S1U']) * 1024,'0.2f')
                self.zdict['S1_max'] = format(float(self.pdict['S1C']) * 1024,'0.2f')
                self.zdict['S1_ratio'] = format(float(self.pdict['S1']),'0.2f')

                self.zdict['Old_used'] = format(float(self.pdict['OU']) * 1024,'0.2f')
                self.zdict['Old_max'] =  format(float(self.pdict['OC']) * 1024,'0.2f')
                self.zdict['Old_ratio'] = format(float(self.pdict['O']),'0.2f')

                self.zdict['Eden_used'] = format(float(self.pdict['EU']) * 1024,'0.2f')
                self.zdict['Eden_max'] = format(float(self.pdict['EC']) * 1024,'0.2f')
                self.zdict['Eden_ratio'] = format(float(self.pdict['E']),'0.2f')            
  
        self.zdict['Perm_used'] = format(float(self.pdict['PU']) * 1024,'0.2f')
        self.zdict['Perm_max'] = format(float(self.pdict['PC']) * 1024,'0.2f')
                self.zdict['Perm_ratio'] = format(float(self.pdict['P']),'0.2f')
               
        self.zdict['Heap_used'] = format((float(self.pdict['EU']) + float(self.pdict['S0U']) + float(self.pdict['S1U'])  + float(self.pdict['OU'])) * 1024,'0.2f')
        self.zdict['Heap_max'] = format((float(self.pdict['EC']) + float(self.pdict['S0C']) + float(self.pdict['S1C'])  + float(self.pdict['OC'])) * 1024,'0.2f')
                self.zdict['Heap_ratio'] = format(float(self.zdict['Heap_used']) / float(self.zdict['Heap_max'])*100,'0.2f')

                self.zdict['YGC'] = self.pdict['YGC']
                self.zdict['FGC'] = self.pdict['FGC']
                self.zdict['YGCT'] = format(float(self.pdict['YGCT']),'0.3f')
                self.zdict['FGCT'] = format(float(self.pdict['FGCT']),'0.3f')
                self.zdict['GCT'] = format(float(self.pdict['GCT']),'0.3f')
            
                if self.pdict['YGC'] == '0':
                   self.zdict['YGCT_avg'] = '0'
                else:
                   self.zdict['YGCT_avg'] = format(float(self.pdict['YGCT'])/float(self.pdict['YGC']),'0.3f')
                if self.pdict['FGC'] == '0':
                   self.zdict['FGCT_avg'] = '0'
                else:
                   self.zdict['FGCT_avg'] = format(float(self.pdict['FGCT'])/float(self.pdict['FGC']),'0.3f')
                if self.pdict['YGC'] == '0' and self.pdict['FGC'] == '0':
                   self.zdict['GCT_avg'] = '0'
                else:
                   self.zdict['GCT_avg'] = format(float(self.pdict['GCT'])/(float(self.pdict['YGC']) + float(self.pdict['FGC'])),'0.3f')
                  

#       print "Dumping zabbix stat dictionary\n-----\n", self.zdict, "\n-----\n"

    def send_to_zabbix(self, metric):
####      {#JAVA_NAME} tomcat_web_1
####      UserParameter=java.discovery,/usr/bin/python /opt/app/zabbix/sbin/java_discovery.py
####      UserParameter=java.discovery_status
  • ,/opt/app/zabbix/sbin/jstat_status.sh $1 $2 $3 $4
    ####      java.discovery_status[tomcat_web_1,Perm_used]
    ####      java.discovery_status[{#JAVA_NAME},Perm_used]
           key = "java.discovery_status[" + self.pdict['jpname'] + "," + metric + "]"

            if self.pdict['pid'] != "" and  send_to_zabbix > 0:
    #                        print key + ":" + self.zdict[metric]
               try:
                                          
                     subprocess.call([zabbix_sender, "-c", zabbix_conf, "-k", key, "-o", self.zdict[metric]], stdout=FNULL, stderr=FNULL, shell=False)  
               except OSError, detail:
                     print "Something went wrong while exectuting zabbix_sender : ", detail
              else:
                 print "Simulation: the following command would be execucted :\n", zabbix_sender, "-c", zabbix_conf, "-k", key, "-o", self.zdict[metric], "\n"


    accepted_modes = ['alive', 'all']


    if len(sys.argv) == 3 and sys.argv[2] in accepted_modes:
        java_name = sys.argv[1]
            mode = sys.argv[2]
    else:
        usage()


    #Check if process is running / Get PID
    jproc = Jprocess(java_name)
    pid = jproc.chk_proc()


    if pid != "" and  mode == 'all':
       jproc.get_jstats()
       jproc.compute_jstats()               
       FNULL = open(os.devnull, 'w')
       for key in jproc.zdict:
           jproc.send_to_zabbix(key)
       FNULL.close()
       print pid

    else:
       print 0





  • 执行这个脚本之前需要使zabbix agent账号具有执行jstat的sudo权限
    1
    2
    3
    echo -e "zabbixagent ALL=(root)      NOPASSWD:/usr/bin/jstat,/opt/app/jdk/bin/jstat" >>/etc/sudoers

    sed -i  's/Defaults    requiretty/Defaults    !requiretty/' /etc/sudoers




    调试结果如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    $ sudo python jstat_status.py elasticsearch all
    java.discovery_status[elasticsearch,Perm_ratio]:99.73
    java.discovery_status[elasticsearch,GCT_avg]:0.040
    java.discovery_status[elasticsearch,S1_max]:200605696.00
    java.discovery_status[elasticsearch,Heap_max]:16106127360.00
    java.discovery_status[elasticsearch,Perm_used]:43855155.20
    java.discovery_status[elasticsearch,Perm_max]:43974656.00
    java.discovery_status[elasticsearch,YGCT_avg]:0.040
    java.discovery_status[elasticsearch,Old_max]:14099611648.00
    java.discovery_status[elasticsearch,S1_ratio]:0.00
    java.discovery_status[elasticsearch,FGCT_avg]:0
    java.discovery_status[elasticsearch,FGC]:0
    java.discovery_status[elasticsearch,Heap_used]:3156421529.60
    java.discovery_status[elasticsearch,Eden_max]:1605304320.00
    java.discovery_status[elasticsearch,Old_used]:2748808294.40
    java.discovery_status[elasticsearch,Eden_used]:392617062.40
    java.discovery_status[elasticsearch,YGC]:8794
    java.discovery_status[elasticsearch,YGCT]:348.801
    java.discovery_status[elasticsearch,Eden_ratio]:25.08
    java.discovery_status[elasticsearch,S0_used]:14996172.80
    java.discovery_status[elasticsearch,FGCT]:0.000
    java.discovery_status[elasticsearch,Old_ratio]:19.50
    java.discovery_status[elasticsearch,Heap_ratio]:19.60
    java.discovery_status[elasticsearch,S0_ratio]:7.48
    java.discovery_status[elasticsearch,S1_used]:0.00
    java.discovery_status[elasticsearch,S0_max]:200605696.00
    java.discovery_status[elasticsearch,GCT]:348.801
    30166





    注释掉调试信息:
    1
    2
    $ sudo python jstat_status.py elasticsearch all
    30166





    添加zabbix配置文件
    1
    2
    3
    $ cat ../conf/zabbix_agentd.conf.d/discovery_java_status.conf
    UserParameter=java.discovery,/usr/bin/python /opt/app/zabbix/sbin/java_discovery.py
    UserParameter=java.discovery_status
  • ,/usr/bin/python /opt/app/zabbix/sbin/jstat_status.py $1 $2









  • 三 添加监控模板
    模板参见附件

    模板中只有一个item是通过zabbix agent获取,其他的都是通过zabbix sender获取


    java.discovery_status[{#JAVA_NAME},all]
    zabbix通过zabbix agent获取这个item的值时会调用jstat_status.py这个脚本通过zabbix_sender

    QQ截图20151102103448.png
    QQ截图20151102103456.png
    QQ截图20151102103516.png






    运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
    2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
    3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
    4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
    5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
    6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
    7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
    8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

    所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-133956-1-1.html 上篇帖子: zabbix监控 下篇帖子: Zabbix通过JMX监控tomcat Java 监控
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    扫码加入运维网微信交流群X

    扫码加入运维网微信交流群

    扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

    扫描微信二维码查看详情

    客服E-mail:kefu@iyunv.com 客服QQ:1061981298


    QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


    提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


    本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



    合作伙伴: 青云cloud

    快速回复 返回顶部 返回列表