首页 > 代码库 > JVM进程状态监控

JVM进程状态监控

前言

==========

为什么需要做服务器jvm自动发现的监控呢?这个事情主要有两点原因:

    1.zabbix默认监控jvm状态是使用jmx中转进行监控的,监控效率比较低下

    2.zabbix使用jmx监控jvm的时候由于一个主机上的键值不能重复,也就导致了一台主机上只能监控一个jvm实例

    以上两点原因导致zabbix通过jmx监控jvm的实现不是很理想,加上最近老大要求收集服务器上面跑的所有java应用的信息,于是自己琢磨了下,还是自己动手,丰衣足食。利用了周末的时间,通过使用shell脚本+java工具jstat+zabbix实现监控主机上多jvm实例的功能。

第一章:概念的理解

 首先,既然要监控jvm状态,那就必须要了解jvm里面的信息,楼主通过搜索资料加自动脑补,把网上的资料取其精华,去其糟粕,整理了一下。JVM中的内存分类分为堆内存和非堆内存,堆内存是给实际应用使用的,非堆内存是给jvm容器使用的。我们主要关心的是堆内存这块。在堆内存里面,给内存分为如下几块:

 1.Young代(年轻代)

 2.Old代(老年代)

 3.Perm代(永久代)(关于这一点,在JDK7和JDK8中情况不一样,将在后面进行分析)

 其中,年轻代里面又分成了三块,如下:

 1.Eden代(伊甸园代)

 2.survivor0代(0号幸存区)

 3.survivor1代(1号幸存区)

 至于更详细的关于JVM堆内存的信息,各位可以自行百度或者google,我这里就不赘述了,毕竟我也是个半桶水,自己找了点资料外加脑补到的一些东西,不敢在关公门前耍大刀了。

 当然,还得科普一个东西,那就是GC,所谓的GC就是JVM在运行的时候会有一个垃圾回收机制,这个垃圾回收机制是什么情况呢?就是在程序运行的时候会产生很多已经不使用的空间,但还是被占用了的情况,这样会造成很多不必要的浪费,于是JVM就有一个垃圾回收机制,针对程序中已经不使用的内存资源,会进行回收释放,这个过程就叫做GC。当然,关于GC还有很多内容我这里也没有详述,理由同上条。各位看官只需要知道GC是JVM监控里面的一个很重要的参数就行了。

 第一章,关于JVM中概念的理解结束了,预知后事如何,请听下回分解。

第二章:JAVA工具的选用

 java工具有很多,关于jvm监控的工具主要有如下几个:

 + jstat

 + jmap

 + jstack

 其中jmap --heap pid可以抓出挺多的关于某个jvm的运行参数,但是老大提醒我最好不要使用jmap进行jvm监控,具体没有说明原因。于是本着打破砂锅问到底的精神,我又去搜了一把,发现了如下内容:

 jmap最主要的危险操作是下面这三种: 

1. jmap -dump 

这个命令执行,JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,并且执行的过程中为了保证dump的信息是可靠的,所以会暂停应用。

2. jmap -permstat 

这个命令执行,JVM会去统计perm区的状况,这整个过程也会比较的耗时,并且同样也会暂停应用。

3. jmap -histo:live 

这个命令执行,JVM会先触发gc,然后再统计信息。

上面的这三个操作都将对应用的执行产生影响,所以建议如果不是很有必要的话,不要去执行。

所以,从上面三点来看,jmap命令对jvm状态影响还是比较大的,而且执行jmap --heap的时间也比较长,效率较低,予以排除。

接下来是jstack,这个命令可以深入到JVM里面对JVM运行问题进行排查,据说还可以统计JVM里面的线程数量。但是这个命令执行效率也比较低,被排除掉了。

于是剩下的只有一个jstat命令了。下面来详细的讲解该命令的使用了,咳咳,各位快点打起点精神来,这可是重头戏来了。

首先,列出jstat命令的一些使用案例吧:

============================================
1.jstat -gc pid
            可以显示gc的信息,查看gc的次数,及时间。
            其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。
S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
9792.0 10048.0  0.0   5143.2 242048.0 220095.4  323200.0   211509.3  186368.0 114451.6    317    4.850   4      0.971    5.821
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1024.0 1024.0  0.0   320.0  11776.0  11604.6   260608.0   149759.6  39344.0 38142.6 4528.0 4303.1   5473   24.010   2      0.128   24.138
2.jstat -gccapacity pid
            可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,
            如 PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,
            PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。
            其他的可以根据这个类推, OC是old内纯的占用量。
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC      PGCMN    PGCMX     PGC       PC     YGC    FGC 
 87360.0 262144.0 262144.0 9792.0 10048.0 242048.0   174784.0   786432.0   323200.0   323200.0 131072.0 262144.0 186368.0 186368.0    317     4
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC 
 1536.0 174592.0  13312.0  512.0  512.0  11776.0   260608.0   349696.0   260608.0   260608.0      0.0 1083392.0  39344.0      0.0 1048576.0   4528.0   5474     2
3.jstat -gcutil pid
            统计gc信息统计。
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
  0.00  51.19  83.29  65.44  61.41    317    4.850     4    0.971    5.821
  
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
 68.75   0.00  46.74  57.47  96.95  95.03   5474   24.014     2    0.128   24.143
4.jstat -gcnew pid
           年轻代对象的信息。
 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT  
9792.0 10048.0    0.0 5143.2  3  15 9792.0 242048.0 198653.2    317    4.850
 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT  
 512.0  512.0  352.0    0.0 15  15  512.0  11776.0   8446.4   5474   24.014
5.jstat -gcnewcapacity pid
           年轻代对象的信息及其占用量。
NGCMN      NGCMX       NGC      S0CMX     S0C     S1CMX     S1C       ECMX        EC      YGC   FGC 
   87360.0   262144.0   262144.0  87360.0   9792.0  87360.0  10048.0   262016.0   242048.0   317     4
NGCMN      NGCMX       NGC      S0CMX     S0C     S1CMX     S1C       ECMX        EC      YGC   FGC 
   1536.0   174592.0    13312.0  57856.0    512.0  57856.0    512.0   173568.0    11776.0  5475     2
6.jstat -gcold pid
          old代对象的信息。
   PC       PU        OC          OU       YGC    FGC    FGCT     GCT   
186368.0 114451.6    323200.0    211509.3    317     4    0.971    5.821
   MC       MU      CCSC     CCSU       OC          OU       YGC    FGC    FGCT     GCT   
 39344.0  38142.6   4528.0   4303.1    260608.0    149783.6   5475     2    0.128   24.148
7.jstat -gcoldcapacity pid
          old代对象的信息及其占用量。
   OGCMN       OGCMX        OGC         OC       YGC   FGC    FGCT     GCT   
   174784.0    786432.0    323200.0    323200.0   317     4    0.971    5.821
   OGCMN       OGCMX        OGC         OC       YGC   FGC    FGCT     GCT   
   260608.0    349696.0    260608.0    260608.0  5475     2    0.128   24.148
   
   
8.jstat -gcpermcapacity pid
          perm对象的信息及其占用量。
  PGCMN      PGCMX       PGC         PC      YGC   FGC    FGCT     GCT   
  131072.0   262144.0   186368.0   186368.0   317     4    0.971    5.821
没有
9.jstat -class pid
          显示加载class的数量,及所占空间等信息。
Loaded  Bytes  Unloaded  Bytes     Time   
 25315 45671.7     5976  7754.1      15.19
Loaded  Bytes  Unloaded  Bytes     Time   
  6472 11893.0        0     0.0       5.97
10.jstat -compiler pid
          显示VM实时编译的数量等信息。
Compiled Failed Invalid   Time   FailedType FailedMethod
    4219      3       0    63.36          1 org/aspectj/weaver/ResolvedType addAndRecurse
Compiled Failed Invalid   Time   FailedType FailedMethod
   11364      1       0   107.53          1 sun/nio/cs/UTF_8$Decoder decode
11.stat -printcompilation pid
          当前VM执行的信息。
Compiled  Size  Type Method
    4219   2232    1 net/spy/memcached/protocol/ascii/BaseGetOpImpl initialize
Compiled  Size  Type Method
   11364    212    1 com/alibaba/rocketmq/client/impl/consumer/RebalanceService run
   ==================================================

   可以看出上面我列出的命令执行结果为什么有两行呢,这是因为是用不同的jdk版本执行的。

   上面是JDK7执行结果,下面是JDK8执行结果,这两个版本之间输出的结果是有差距的,下面,就来分析为什么会产生这种差异。

JDK7和JDK8中JVM堆内存划分差异

   如果记性好的童鞋们应该还能记得我上面在介绍JVM堆内存分类的时候括号里写的那个东东吧,没错,就是这个东西导致的。在JDK7中的Perm代(永久代)在JDK8中被废除了,取而代之的是Metadata代(元数据代),据说这个元数据代相对于永久代进行了优化,如果不设置最大值的话,默认会按需增长, 不会造成像Perm代中内存占满后会爆出内存溢出的错误,元数据代也可以设置最大值,这样的话,当内存区域被消耗完的时候将会和Perm代一样爆出内存溢出的错误。(PS:原谅我的班门弄斧,只能解释到这一个层面了。)

好了,解释清楚了JDK7和JDK8的差异以后,接下来我们来解释jstat抓到的这些参数了。

jstat命令获取参数解析
======================================================================================
* S0C 年轻代中第一个survivor(幸存区)的容量 (字节)jstat -gcnew $pid|tail -1|awk ‘{print $1*1024}‘
* S0U 年轻代中第一个survivor(幸存区)目前已使用空间 (字节)jstat -gcnew $pid|tail -1|awk ‘{print $3*1024}‘
* S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk ‘{print $1}‘
* S0CMX 年轻代中第一个survivor(幸存区)的最大容量 (字节)jstat -gcnewcapacity $pid|tail -1|awk ‘{print $4*1024}‘
* 
* S1C 年轻代中第二个survivor(幸存区)的容量 (字节)jstat -gcnew $pid|tail -1|awk ‘{print $2*1024}‘
* S1U 年轻代中第二个survivor(幸存区)目前已使用空间 (字节)jstat -gcnew $pid|tail -1|awk ‘{print $4*1024}‘
* S1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk ‘{print $2}‘
* S1CMX  年轻代中第二个survivor(幸存区)的最大容量 (字节)jstat -gcnewcapacity $pid|tail -1|awk ‘{print $6*1024}‘
* DSS 当前需要survivor(幸存区)的容量 (字节)(Eden区已满)jstat -gcnew $pid|tail -1|awk ‘{print $7*1024}‘
* 
* EC 年轻代中Eden(伊甸园)的容量 (字节)jstat -gcnew $pid|tail -1|awk ‘{print $8*1024}‘
* EU 年轻代中Eden(伊甸园)目前已使用空间 (字节)jstat -gcnew $pid|tail -1|awk ‘{print $9*1024}‘
* ECMX 年轻代中Eden(伊甸园)的最大容量 (字节)jstat -gcnewcapacity $pid|tail -1|awk ‘{print $8*1024}‘
* E 年轻代中Eden(伊甸园)已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk ‘{print $3}‘
* 
* NGCMN 年轻代(young)中初始化(最小)的大小 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $1*1024}‘
* NGCMX 年轻代(young)的最大容量 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $2*1024}‘
* NGC 年轻代(young)中当前的容量 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $3*1024}‘
* 
* OC Old代的容量 (字节)jstat -gcold $pid|tail -1|awk ‘{print $3*1024}‘
* OU Old代目前已使用空间 (字节)jstat -gcold $pid|tail -1|awk ‘{print $4*1024}‘
* OGCMX old代的最大容量 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $8*1024}‘
* OGCMN old代中初始化(最小)的大小 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $7*1024}‘
* O old代已使用的占当前容量百分比jstat -gcutil $pid|tail -1|awk ‘{print $4}‘
* OGC old代当前新生成的容量 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $9*1024}‘
* 
* PC Perm(持久代)的容量 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $14*1024}‘
* PU Perm(持久代)目前已使用空间 (字节)jstat -gc $pid|tail -1|awk ‘{print $10*1024}‘
* PGCMX perm代的最大容量 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $12*1024}‘
* PGCMN perm代中初始化(最小)的大小 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $11*1024}‘
* P perm代已使用的占当前容量百分比 jstat -gcutil $pid|tail -1|awk ‘{print $5*1024}‘
* PGC perm代当前新生成的容量 (字节)jstat -gccapacity $pid|tail -1|awk ‘{print $13*1024}‘
* 
* YGC 从应用程序启动到采样时年轻代中gc次数jstat -gccapacity $pid|tail -1|awk ‘{print $15}‘
* YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)jstat -gcutil $pid|tail -1|awk ‘{print $7}‘
* FGC从应用程序启动到采样时old代(全gc)gc次数jstat -gccapacity $pid|tail -1|awk ‘{print $16}‘
* FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)jstat -gcutil $pid|tail -1|awk ‘{print $9}‘
* GCT 从应用程序启动到采样时gc用的总时间(s)jstat -gcutil $pid|tail -1|awk ‘{print $10}‘
* 
* TT  持有次数限制jstat -gcnew $pid|tail -1|awk ‘{print $5}‘
* MTT   最大持有次数限制jstat -gcnew $pid|tail -1|awk ‘{print $6}‘
*
* Loadedjvm加载class数量
* Unloadedjvm未加载class数量
*
* M元数据区使用比例
* MC当前元数据空间大小
* MU元数据空间使用大小
* MCMN最小元数据容量 
* MCMX最大元数据容量
* 
* CCS压缩使用比例
* CCSC当前压缩类空间大小
* CCSU压缩类空间使用大小
* CCSMN最小压缩类空间大小
* CCSMX最大压缩类空间大小
====================================================

好了,上面就是我找到的一些对jstat获取的数据意思的统计,各位看官可以做个参考。

好了,这一章的内容到此基本结束,前面的东西都是一些理论类的东西,没有实际的操作。俗话说,光说不练假把式。接下来,我们将开启下一章的旅程,脚本+jstat的使用。

第三章:脚本+jstat获取数据

首先,我们来看一下该章节介绍的几个脚本吧:

1.jvm_list.sh 获取该机器上所有运行的JVM的进程对应的程序根目录以及程序名称

2.get_jvmlist.sh 将获取的该机器上的所有进程对应的程序名称序列化成json格式并发送给zabbix服务器

3.get_jvmstatus.sh 通过获取的程序根目录获取到对应的程序进程,再通过jstat抓取数据写入到文件中缓存

4.set_jvmstatus.sh zabbix通过调用该脚本获取缓存文件中的关于某个JVM进程的状态信息

好了,简单介绍了上面几个脚本的功能,下面我们列出这几个脚本的实际内容:

    #cat jvm_list.sh 
    #!/bin/bash
    
    packagePath=/usr/local/etc/scripts/package_path.txt
    echo -n >$packagePath
    
    for i in `ps -fC java|tail -n +2|grep -v ‘flume‘|awk ‘{print $2}‘`;
    do
            pgrootpath=`ls -l /proc/$i/cwd|awk ‘{print $NF}‘`
            if [[ -r $pgrootpath/appconfig ]] && [  `grep ^packagename= $pgrootpath/appconfig|wc -l`==1 ];then
                            packagename=$(grep ^packagename= $pgrootpath/appconfig 2>/dev/null|awk -F‘"‘ ‘{print $2}‘)
            elif [[ -r $pgrootpath/webconfig ]] && [  `grep ^packagename= $pgrootpath/webconfig|wc -l`==1 ];then
                            packagename=$(grep ^packagename= $pgrootpath/webconfig 2>/dev/null|awk -F‘"‘ ‘{print $2}‘)
            else
                    packagename=$(basename $pgrootpath)-1.0.0-bin.tar.gz
            fi
    
            echo "$packagename $pgrootpath" >> $packagePath
    done

 

该脚本的目的是先通过使用ps -fC java命令获取该机器上面除了flume进程外的所有其他java进程(我这边使用的是flume来收集业务日志的。)

然后,通过获取到的PID使用ll /proc/pid/cwd命令获取该进程的程序根目录,后面那些判断是获取该进程对应的包名(这一步各位可以根据自己公司的情况自行修改,我这边取包名的方式并不能够匹配各位公司的设置,在下爱莫能助了。)

最后是将获取到的程序根目录和包名存放在变量packagePath对应的文件中。

    #cat get_jvmlist.sh 
    #!/bin/bash
    
    TABLESPACE=`awk ‘{print $1}‘ /usr/local/etc/scripts/package_path.txt`
    COUNT=`echo "$TABLESPACE" |wc -l`
    INDEX=0
    echo ‘{"data":[‘
    echo "$TABLESPACE" | while read LINE; do
        echo -n ‘{"{#TABLENAME}":"‘$LINE‘"}‘
        INDEX=`expr $INDEX + 1`
        if [ $INDEX -lt $COUNT ]; then
            echo ‘,‘
        fi
    done
    echo ‘]}‘

这个脚本的作用就是通过读取文件里面的包名,然后将包名进行json序列化输出,没什么好讲的,套路套一个循环脚本就行。

接下来就是重要的脚本了,调用jstat获取JVM状态,并缓存到文件中。

    #cat get_jvmstatus.sh 
    #!/bin/bash
    
    MAINCLASS="*Main.class"
    scriptPath=/usr/local/etc/scripts
    
    cat $scriptPath/package_path.txt|while read line
    do
    packageName=$(echo $line|awk ‘{print $1}‘)
    pgRootPath=$(echo $line|awk ‘{print $2}‘)
    if [[ -d $pgRootPath/tomcat ]];then
    pid=$(cat $pgRootPath/tomcat/tomcat.pid)
    else
    mainPath=$(find $pgRootPath -name $MAINCLASS)
    appName=$(echo ${mainPath##*classes/}|sed ‘s#/#.#g‘|sed ‘s#.class##g‘)
    pid=$(ps -fC java|grep "$appName"|awk ‘{print $2}‘)
    fi
    javaHome=/usr/local/java/jdk1.8.0
    #javaHome=/usr/local/java/latest
    #if [[ -r $pgRootPath/appconfig ]] && [  `grep ^JAVA_HOME= $pgRootPath/appconfig|wc -l` == 1 ] && [ `grep ^JAVA_HOME= $pgRootPath/appconfig|grep 8|wc -l` == 1 ];then
                            #javaHome=$(grep ^JAVA_HOME= $pgRootPath/appconfig 2>/dev/null|awk -F‘=‘ ‘{print $2}‘)
    #javaHome=/usr/local/java/jdk1.8.0
            #else
            #        if [[ -r $pgRootPath/webconfig ]] && [ `grep ^‘export JAVA_HOME=‘ $pgRootPath/webconfig|wc -l` == 1 ] && [ `grep ^‘export JAVA_HOME=‘ $pgRootPath/webconfig|grep 8|wc -l` == 1 ];then
            #                #javaHome=$(grep ^‘export JAVA_HOME=‘ $pgRootPath/webconfig 2>/dev/null|awk -F‘"‘ ‘{print $2}‘)
            #        javaHome=/usr/local/java/jdk1.8.0
    #fi
    #fi
    #echo --------------------------------$pgRootPath
    #echo $javaHome
    echo -------------------------------$pid
    sleep 5
    #echo -n >$scriptPath/package/$packageName
    #$javaHome/bin/jstat -gccapacity $pid > ./package/$packageName 2>/dev/null
    #$javaHome/bin/jmap -heap $pid>>./package/$packageName 2>/dev/null
    echo gcnew >> $scriptPath/package/$packageName 2>/dev/null
    $javaHome/bin/jstat -gcnew $pid >> $scriptPath/package/$packageName 2>/dev/null
    echo gcutil >> $scriptPath/package/$packageName 2>/dev/null
    $javaHome/bin/jstat -gcutil $pid >> $scriptPath/package/$packageName 2>/dev/null
    echo gcnewcapacity >> $scriptPath/package/$packageName 2>/dev/null
    $javaHome/bin/jstat -gcnewcapacity $pid >> $scriptPath/package/$packageName 2>/dev/null
            echo gccapacity >> $scriptPath/package/$packageName 2>/dev/null
    $javaHome/bin/jstat -gccapacity $pid >> $scriptPath/package/$packageName 2>/dev/null
            #echo gcold >> $scriptPath/package/$packageName 2>/dev/null
    #$javaHome/bin/jstat -gcold $pid >> $scriptPath/package/$packageName 2>/dev/null
            echo gc >> $scriptPath/package/$packageName 2>/dev/null
    $javaHome/bin/jstat -gc $pid >> $scriptPath/package/$packageName 2>/dev/null
            echo class >> $scriptPath/package/$packageName 2>/dev/null
    $javaHome/bin/jstat -class $pid >> $scriptPath/package/$packageName 2>/dev/null
    echo cpu >> $scriptPath/package/$packageName 2>/dev/null
    echo -e "CPU\n$( ps aux|grep $pid|grep -v grep|awk ‘{print $3}‘)" >> $scriptPath/package/$packageName 2>/dev/null
    echo mem >> $scriptPath/package/$packageName 2>/dev/null
    echo -e "MEM\n$( ps aux|grep $pid|grep -v grep|awk ‘{print $6}‘)" >> $scriptPath/package/$packageName 2>/dev/null
    
    done

这里面首先是通过获取到程序的根目录,然后我这的java程序除了tomcat跑的之外,其他的java程序都是通过Main.class启动的,所以可以获取到AppName,这样通过ps命令就能找到其对应的PID了,而如果是tomcat启动的进程的话,在程序根目录下面的tomcat目录下有一个tomcat.pid文件里面有该程序的PID。后面被注释的那一端代码其实之前是加上去的,那段代码的作用是判断该进程使用的是JDK7还是JDK8启动的,当初的计划是想着如果是JDK7启动的进程就用JDK7的jstat去获取数据,如果是JDK8启动的进程就用JDK8的jstat去获取数据,后来发现不同版本的JDK获取的数据格式不同,于是。。。。。。后悔莫及的把那段代码注释掉了。后面综合公司实际情况考虑,JDK8的程序用得比较多,JDK7的程序相对来说比较少,并且慢慢都会向JDK8进行转换,所以,权衡利弊之下,之后将jstat的JDK全部换成了JDK8,这样的影响就是获取不到JDK7的永久代数据。当然,各位有兴趣的话,也可以JDK7和JDK8同时使用,在过滤输出文件的时候加一个标志位进行判断,当然,我这里暂时没有做这方面的修改。。。毕竟时间有限。。。

第四个脚本,个人感觉写的最烂的一个脚本。。。但是。。。没办法,技术水平有限,各位将就着看吧(捂脸哭)

    # cat set_jvmstatus.sh 
    #!/bin/bash
    packageName=$1
    key=$2
    
    if [ $2 == "S0C" -o $2 == "S0U" -o $2 == "S1C" -o $2 == "S1U" -o $2 == "DSS" -o $2 == "EC" -o $2 == "EU" ];then
    part=gcnew
    elif [ $2 == "S0" -o $2 == "S1" -o $2 == "E" -o $2 == "O" -o $2 == "M" -o $2 == "CCS" -o $2 == "YGCT" -o $2 == "FGCT" -o $2 == "GCT" ];then
    part=gcutil
    elif [ $2 == "S0CMX" -o $2 == "S1CMX" -o $2 == "ECMX" ];then
    part=gcnewcapacity
    elif [ $2 == "NGCMN" -o $2 == "NGCMX" -o $2 == "NGC" -o $2 == "OGCMX" -o $2 == "OGCMN" -o $2 == "OGC" -o $2 == "MCMN" -o $2 == "MCMX" -o $2 == "MC" -o $2 == "CCSMN" -o $2 == "CCSMX" -o $2 == "CCSC" -o $2 == "YGC" -o $2 == "FGC" ];then
    part=gccapacity
    elif [ $2 == "MU" -o $2 == "CCSU" -o $2 == "OC" -o $2 == "OU" ];then
    part=gc
    elif [ $2 == "Loaded" -o $2 == "Unloaded" ];then
    part=class
    elif [ $2 == "CPU" ];then
    part=cpu
    elif [ $2 == "MEM" ];then
            part=mem
    else
    echo "Error input:"
    exit 0
    fi
    case $2 in
    S0C)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $1*1024}‘
    ;;
    S0U)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $3*1024}‘
    ;;
    S0)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $0}‘
    ;;
    S0CMX)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $4*1024}‘
    ;;
    S1C)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $2*1024}‘
    ;;
    S1U)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $4*1024}‘
    ;;
    S1)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $2}‘
    ;;
    S1CMX)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $6*1024}‘
    ;;
    DSS)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $7*1024}‘
    ;;
    EC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $8*1024}‘
    ;;
    EU)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $9*1024}‘
    ;;
    ECMX)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $8*1024}‘
    ;;
    E)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $3}‘
    ;;
    NGCMN)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $1*1024}‘
    ;;
    NGCMX)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $2*1024}‘
    ;;
    NGC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $3*1024}‘
    ;;
    OC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $7*1024}‘
    ;;
    OU)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $8*1024}‘
    ;;
    OGCMX)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $8*1024}‘
    ;;
    OGCMN)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $7*1024}‘
    ;;
    O)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $4}‘
    ;;
    OGC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $9*1024}‘
    ;;
    M)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $5}‘
    ;;
    MC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $13*1024}‘
    ;;
    MU)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $10*1024}‘
    ;;
    MCMN)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $11*1024}‘
    ;;
    MCMX)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $12*1024}‘
    ;;
    CCS)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $6}‘
    ;;
    CCSC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $13*1024}‘
    ;;
    CCSU)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $12*1024}‘
    ;;
    CCSMN)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $14*1024}‘
    ;;
    CCSMX)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $15*1024}‘
    ;;
    YGC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $17}‘
    ;;
    YGCT)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $8}‘
    ;;
    FGC)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $18}‘
    ;;
    FGCT)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $10}‘
    ;;
    GCT)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $11}‘
    ;;
    TT)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $5}‘
    ;;
    MTT)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $6}‘
    ;;
    Loaded)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $1}‘
    ;;
    Unloaded)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $3}‘
    ;;
    CPU)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%f\n", $1}‘
    ;;
    MEM)
    grep -wA 2 ^"$part" /usr/local/etc/scripts/package/$1|tail -1|awk ‘{printf "%d\n", $1*1024}‘
    ;;
    *)
    echo "Error input:"
    ;;
    esac
    exit 0

这套脚本没什么讲的,就是重复的进行一些判断,抓数据并输出(注意,之前写的获取的jstat参数的值其实是不准确的,获取的值是以KB为单位而不是以字节为单位,所以我取完数据后对数据进行成字节为单位了。)

接下来,讲一下这几个脚本该怎么部署。我这里的zabbix_agentd是通过yum安装的,所以安装在/usr/local目录下,配置文件在/usr/local/etc目录下,需要在zabbix_agentd.conf里面添加下面两行获取数据的key(注意,添加好后一定要记得重启zabbix_agentd进程):

UserParameter=jmx.discovery,/usr/local/etc/scripts/get_jvmlist.sh
UserParameter=jmx.resource[*],/usr/local/etc/scripts/set_jvmstatus.sh $1 $2

然后脚本都放置在/usr/local/etc/scripts/目录下,该目录下的脚本权限如下:

 -rwxr-xr-x 1 zabbix zabbix  326 3月  26 22:29 get_jvmlist.sh
 -rwxr-xr-x 1 root   root   2956 3月  28 20:57 get_jvmstatus.sh
 -rwxr-xr-x 1 root   root    818 3月  26 22:33 jvm_list.sh
 drwxr-xr-x 2 zabbix zabbix 4096 3月  26 23:05 package
 -rw-r--r-- 1 zabbix zabbix 1947 3月  29 11:23 package_path.txt
 -rwxr-xr-x 1 zabbix zabbix 5240 3月  28 20:50 set_jvmstatus.sh

然后需要在crontab里面定义jvm_list.sh和get_jvmstatus.sh脚本的定时任务,我这里定义的如下:

* */1 * * * /usr/local/etc/scripts/jvm_list.sh
*/5 * * * * /usr/local/etc/scripts/get_jvmstatus.sh

注意这两个脚本必须要以root权限去执行,因为里面涉及到的一些命令只有root用户才有权限去执行。

之后可以手动执行脚本去获取数据,看是否能够抓取到相应的数据。

好了,这章的脚本讲完了,下一章,就是怎样通过zabbix获取相应的数据了。

第四章:zabbix获取数据

 

通过之前的脚本部署,可以在zabbix_server上面通过zabbix_get命令去检查是否获取到了相应的数据:

 

    # zabbix_get  -s xx.xx.xx.xx -k jmx.resource[Abcdefg-1.0.0-rc-bin.tar.gz,MEM]

    641036288

我这里可以获取到数据了(注意IP被我注释掉了,为了保护隐私哈,包名也被我刻意修改了,隐私隐私哈)

接下来就可以部署模板了,至于模板我已经做好了,可以直接在附件里面下载。至于模板我制作了一些简单的key的值收集,以及图像的展示,至于监控报警值的设置,由于各个公司的环境不一样,需要各位自己根据自己需求自行设置。

JVM进程状态监控