########################################################################
# JBoss, Home of Professional Open Source
# Copyright 2010, Red Hat and individual contributors
# by the @authors tag. See the copyright.txt in the distribution for a
# full listing of individual contributors.
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this software; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA, or see the FSF site: http://www.fsf.org.
#
# @authors Andrew Dinn
#
# JVM Statistics Display via a Dynamic MBean
#
# A variant of the PeriodicStats script which counts the occurence of
# various events in the JVM and makes the stats available via a JMX
# Dynamic MBean
#
# to use ths script to trace execution of java program org.my.App execute
#
#  -- set the directory in which byteman has been installed
#  BYTEMAN_HOME= ...
#
#   -- identify the samples helper jar
#   SAMPLE_JAR=${BYTEMAN_HOME}/sample/lib/byteman-sample.jar
#
#   -- identify this script
#   SCRIPT={BYTEMAN_HOME}/sample/scripts/JVMMBeanStats.txt
#
#  ${BYTEMAN_HOME}/bin/bmjava.sh -l $SCRIPT -b $SAMPLE_JAR org.my.App
#
# alternatively to load the script dynamically
#
#   -- start the program with the agent
#  ${BYTEMAN_HOME}/bin/bmjava.sh org.my.App
#
#   -- install the helper library into the bootstrap classpath
#  ${BYTEMAN_HOME}/bin/bmsubmit.sh -b $SAMPLE_JAR
#
#   -- install the script
#  ${BYTEMAN_HOME}/bin/bmsubmit.sh -l $SCRIPT

########################################################################
#
# All rules in this script use class JMXHelper which provides support
# for sampling and displaying statistics in an MBean. The helper adds a
# thread in the background when it is activated i.e. when any of the rules
# which employs the helper is first triggered. The thread is shut down
# when the helper is deactivated i.e. once all rules using the helper
# have been uninstalled. At regular intervals the helper thread samples
# counters updated by the rules and posts new stats to the MBean.
# The helper will, by default, install its MBeans in the platform
# MBeanServer. You can override this behavior by setting the JVM
# system property "org.jboss.byteman.jmx.mbeanserver" to a valid
# JMX domain name. If an MBeanServer exists with that as its default
# domain, it will be used, otherwise, an MBeanServer will be created
# with that domain name as its default. If the system property is
# set to "*platform*", then the platform MBeanServer will be used.
#
# The script attaches a rule to the helper method keyInfo() to define the
# counters to be sampled by the background thread. The object returned
# by this rule contains a set of string keys identifying the counters.
# It also provides each counter with a corresponding desciption
# and counter type. The type is either: CUMULATIVE meaning that the
# display tracks the counter total across successive samples; RATE,
# meaning that the display tracks the rate of change in the counter
# value per second across the last N samples; or MEAN, meaning
# that the display tracks the mean value of the counter across
# the last N samples. n.b. in the last case the sample counts
# are weighted by the exact length of the sample interval.
#
# The script also attaches a rule to helper method samplePeriod. The value
# 5000 returned by this method is used as the sampling period measured in
#  milliseconds. If this rule were omitted then the method would returns
# the default value 10,000.
#
# Thirdly, the script attaches a rule to helper method sampleSetSize. The value
# 3 returned by this method is used to determine how many sample readings to
# combine when computing the rate of change of a counter or the average value
# of the counter across each sample period.
#
# The remaining rules are used to update the values of the counters displayed
# in the mbean. They are injected into methods at locations where
# a statistically significant event occurs and their action, fired when
# that event needs to be counted, is to incremment the relevant counter.
#
# In this example the rules measure various operations in the JVM such as
# thread, file and class loads operations.

# ensure all rules employ the JMX helper class.

HELPER org.jboss.byteman.sample.helper.JMXHelper

# this rule is triggered when the periodic helper thread starts
# it returns a KeyInfo object identifying the stats counters
# updated by rules in this rule set

RULE return key info
CLASS JMXHelper
METHOD keyInfo()
BIND keyInfo : KeyInfo = new KeyInfo("JVM Statistics in a Dynamic MBean")
IF TRUE
DO keyInfo.addKey("thread creates", KeyInfo.KEY_TYPE_CUMULATIVE, "Thread() total calls");
   keyInfo.addKey("thread starts", KeyInfo.KEY_TYPE_CUMULATIVE, "Thread.start() total calls");
   keyInfo.addKey("thread runs", KeyInfo.KEY_TYPE_CUMULATIVE, "Thread.run() total calls");
   keyInfo.addKey("thread exits", KeyInfo.KEY_TYPE_CUMULATIVE, "Thread.exit() total calls");
   keyInfo.addKey("thread creates", KeyInfo.KEY_TYPE_RATE, "Thread() calls per second");
   keyInfo.addKey("thread starts", KeyInfo.KEY_TYPE_RATE, "Thread.start() calls per second");
   keyInfo.addKey("thread runs", KeyInfo.KEY_TYPE_RATE, "Thread.run() calls per second");
   keyInfo.addKey("thread exits", KeyInfo.KEY_TYPE_RATE, "Thread.exit() calls per second");
   keyInfo.addKey("file in opens", KeyInfo.KEY_TYPE_RATE, "FileInputStream.open() calls per second");
   keyInfo.addKey("file out opens", KeyInfo.KEY_TYPE_RATE, "FileOutputStream.open() calls per second");
   keyInfo.addKey("file in closes", KeyInfo.KEY_TYPE_RATE, "FileInputStream.close() calls per second");
   keyInfo.addKey("file out closes", KeyInfo.KEY_TYPE_RATE, "FileOutputStream.close() calls per second");
   keyInfo.addKey("class loads", KeyInfo.KEY_TYPE_CUMULATIVE, "ClassLoader.defineClass() total calls");
   keyInfo.addKey("class loads", KeyInfo.KEY_TYPE_MEAN, "ClassLoader.defineClass() mean calls per sample");
   RETURN keyInfo
ENDRULE

# this rule is triggered when the periodic helper thread starts
# it returns a sample period in milliseconds for which the
# periodic helper thread sits and waits before sampling each
# of the counters anf updating the MBean

RULE set period
CLASS JMXHelper
METHOD samplePeriod()
IF TRUE
DO RETURN 5000
ENDRULE

# this rule is triggered when the periodic helper thread starts
# it returns a count for the number of samples which will be
# combined when computing counter rates or counter sample period
# means

RULE set sample set size
CLASS JMXHelper
METHOD sampleSetSize()
IF TRUE
DO RETURN 3
ENDRULE

# This rule is triggered when the periodic helper thread starts
# it returns a boolean which determines whether the helper thread
# creates a JMX Connector Server, allowing the MBean stats to be
# fetched by a remote client using RMI. The default implemenation of
# the trigger method returns false. If you want to be able to fetch
# the MBean stats form a remote client uncomment this rule so  that
# it injects code returning true.
# RULE configure rmi server
# CLASS JMXHelper
# METHOD rmiServerRequired()
# IF TRUE
# DO RETURN TRUE
# ENDRULE

# This rule is triggered when the periodic helper thread starts
# but only if the previous rule has requested that an rmi server
# be installed. it returns a host address used when creating the
# server socket. the trigger method returns "localhost" as the
# default host. if you want to use a  different host then uncommment
# this rule and insert a suitable address
# RULE configure rmi host
# CLASS JMXHelper
# METHOD rmiHost()
# IF TRUE
# DO RETURN "<replace me with a hostname or address>"
# ENDRULE

# This rule is triggered when the periodic helper thread starts
# but only if the previous rule has requested that an rmi server
# be installed. it returns a port used when creating the
# server socket. the trigger method returns 9999 as the
# default host. if you want to use a  different host then uncommment
# this rule and edit the port number
# RULE configure rmi host
# CLASS JMXHelper
# METHOD rmiHost()
# IF TRUE
# DO RETURN <replace me with a port number>
# ENDRULE

# The remaining rules increment stats counters whenever a sigificant
# event occurs in the JVM.

# this rule counts Thread creates
RULE count thread create
CLASS java.lang.Thread
METHOD <init>
AT EXIT
IF TRUE
DO debug("create thread " + $0.getName());
   incrementCounter("thread creates")
ENDRULE

# this rule counts Thread starts
RULE count thread start
CLASS java.lang.Thread
METHOD start()
AT CALL start0
IF TRUE
DO debug("start thread " + $0.getName());
   incrementCounter("thread starts")
ENDRULE

# this rule counts Thread runs
RULE count thread run
CLASS ^java.lang.Thread
METHOD run()
# only count run when called from JVM i.e. when there is no caller frame
IF !callerMatches(".*")
DO debug("run thread " + $0.getName());
   incrementCounter("thread runs")
ENDRULE

# this rule counts Thread exits
RULE count thread exit
CLASS ^java.lang.Thread
METHOD exit()
IF TRUE
DO debug("exit thread " + $0.getName());
   incrementCounter("thread exits")
ENDRULE

# this rule counts FileInputStream opens for read from a File
RULE count file open read File
CLASS java.io.FileInputStream
METHOD <init>(java.io.File)
# only count successful opens i.e. ones where we reach return
AT RETURN
IF TRUE
DO debug("open input file " + $1.getName() + " " + $0.fd);
   incrementCounter("file in opens")
ENDRULE

# this rule counts FileInputStream opens for read from a File Descriptor
RULE count file open read File Descriptor
CLASS java.io.FileInputStream
METHOD <init>(FileDescriptor)
# only count successful opens i.e. ones where we reach return
AT RETURN
IF TRUE
DO debug("open input file 2 " + $1);
   incrementCounter("file in opens")
ENDRULE

# this rule counts FileOutputStream opens for write from a File
RULE count file open write File
CLASS java.io.FileOutputStream
METHOD <init>(java.io.File, boolean)
# only count successful opens i.e. ones where we reach return
AT RETURN
IF TRUE
DO debug("open output file " + $1.getName() + " " + $0.fd);
   incrementCounter("file out opens")
ENDRULE

# this rule counts FileOutputStream opens for write from a File Descriptor
RULE count file open write File Descriptor
CLASS java.io.FileOutputStream
METHOD <init>(FileDescriptor)
# only count successful opens i.e. ones where we reach return
AT RETURN
IF TRUE
DO debug("open output file 2 " + $1);
   incrementCounter("file out opens")
ENDRULE

# this rule counts FileInputStream closes for a File input stream
RULE count file input stream close
CLASS ^java.io.FileInputStream
METHOD close()
AT RETURN
BIND nullfd : java.io.FileDescriptor = null
# don't count if this is a call to super or if the file has already been closed
IF !callerMatches(".*\\.close") && $0.fd != nullfd
DO debug("close input file " + $0.fd.fd);
   incrementCounter("file in closes")
ENDRULE

# this rule counts FileOutputStream closes for a File output stream
RULE count file output stream close
CLASS ^java.io.FileOutputStream
METHOD close()
AT RETURN
BIND nullfd : java.io.FileDescriptor = null
 # don't count if this is a call to super or if the file has already been closed
IF !callerMatches(".*\\.close")  && $0.fd != nullfd
DO debug("close output file " + $0.fd.fd);
   incrementCounter("file out closes")
ENDRULE

# this rule counts class loads which result in a new class being defined
RULE count class loads
CLASS ^java.lang.ClassLoader
METHOD defineClass(String, byte[], int, int, ProtectionDomain)
# only count successful loads
AT RETURN
IF TRUE
DO debug("define class " + $1);
   incrementCounter("class loads")
ENDRULE




