Skip to content

Repository files navigation

mjprof

mjprof is a command line monadic java profiler.

Introduction

mjprof is a monadic thread dump analysis tool set. It is a fancy way to say it analyzes jstack output using a series of simple composable building blocks (monads).

Motivation

So, You are out there in the wild vs a production machine. All you have in hand is the "poor man's profiler" jstack. You take one, two, three jstack thread dumps and then you need to look at them manually inside an editor, vi, less, etc.... If you have done it enough times, you probably know that it is a lot of manual work. Especially when you have thousands of threads in your process.

Running mjprof

mjprof reads thread dumps from one or more data sources and writes to the standard output. An example of a data source can be the standard input.

The following will filter out all the threads which are not in RUNNABLE state:
jstack -l pid | ./mjprof.sh contains/state,RUNNABLE/

The commands passed to mjprof consists of several building blocks, monads from now on, concatenated with , (comma) While the monads are relatively simple, mixing and matching them can yield a very thorough analysis, while still allowing you to focus on the data you need. Monad parameters are wrapped with / (instead of () {} or [] which are special chars in the shell) and seperated by ,

Monads

Data Sources

Data sources generate thread dumps and feed them into mjprof. The default data source is stdin. When no data source is specified stdin will be used. When more than one data source is specified the thread dumps generated by all of them will be fed into mjprof.

  • jmx/host:port|MainClass|pid,[count],[sleep],[username],[passwd]/ - Generate dumps via JMX
  • jmxc/host:port|MainClass|pid,[count],[sleep],[username],[passwd]/ - Generate thread dumps via JMX and collect sper thread CPU
  • path/path/ - Read thread dump from file
  • stdin - Read thread dumps from standard input
  • visualvm/path/ - Read profiling session from xml export of VisualVM

Output

  • gui/[title],[maxInvocations]/ - Display current thread dump in a GUI window
  • snapshot/[filename]/ - Write to a file
  • stdout - Writes current stream of thread dumps to stdout

Filters

  • contains/attr,value/ - Returns only threads which contain the string in certain attribute (regexp not supported)
  • -contains/attr,value/ - Returns only threads which do not contain the string (regexp not supported)

Single thread mappers

  • -at - Eliminates the 'at' from the beginning of stack frames
  • bottom/int/ - Returns at most n bottom stack frames of the stack
  • -fn - Eliminates file name and line from stack frames
  • frame/string/ - Eliminates stack frames from all stacks which do not contain string.
  • -frame/string/ - Eliminates stack frames from all stacks which contain string.
  • -namesuffix - Trim the last number from thread names helps to group thread pool threads together
  • noop - Does nothing
  • -pkg - Eliminates package name from stack frames
  • -prop/attr/ - Removes a certain attribute e.g. eliminate/stack/
  • top/int/ - Returns at most n top stack frames of the stack
  • trimbelow/string/ - Trim all stack frames below the first occurrence of string

Full dump mappers:

  • group/[attr]/ - Group a single thread dump by an attribute. If not attribute is specified all dump is merged
  • merge/attribute/ - Combine all dumps to a single one merge based on thread id attribute
  • sort/attr/ - Sorts based on an attribute
  • -sort/string/ - Sorts based on an attribute (descending order)

Fixed: sort/-sort used to throw a NullPointerException and crash the whole run when any thread lacked the sorted attribute (e.g. mjprof path/dump.txt/.sort/state/ on an ordinary dump). Threads missing the attribute now sort last, in both ascending and descending order, instead of crashing.

Terminals:

  • count - counts number of threads

  • ctree - combine all stack traces with colors (UNIX Terminal)

  • flat - Shows flat histogram of the profiles

  • list - lists the possible stack trace attributes

  • tree - combine all stack traces

  • help -Prints this message

Macros:

  • blocked - contains/state,BLOCKED/
  • jvm - ncontains/stack,at/
  • -jvm - contains/stack,at/.ncontains/name,RMI TCP /.ncontains/name,Reference Handle/.ncontains/name,Finalizer/.ncontains/name,JMX server connection timeout/.ncontains/name,RMI Scheduler/
  • locks - stackkeep/- /
  • -locks - stackelim/- /
  • parking - contains/state,TIMED_WAITING/.contains/stack,sun.misc.Unsafe.park/
  • running - contains/state,RUNNABLE/
  • sleeping - contains/state,TIMED_WAITING/
  • waiting - contains/state,WAITING/
  • withstack - contains/stack,at/

Properties

Properties may change from one dump to another and the can also be eliminated by mjstack. Following is the list of usual properties

  • status - The status of the thread
  • nid - Native thread id ( a number)
  • name - Name of thread
  • state - State of thread
  • los - The locked ownable synchronizers part of the stack trace
  • daemon - Whether the thread is a daemon or not
  • tid - The thread id (a number)
  • prio - Thread priority, a number
  • stack - The actual stack trace
  • cpu_ns - cpu consumed in nano seconds
  • wall_ms - wall time in ms for the time frame cpu was recorded
  • %cpu - %cpu of the thread info

You can also get the actual list of properties bu using the list monad.
jstack -l pid | ./mjprof list

Examples

jstack Original output:
jstack -l 38515 > mystack.txt
Keep only thread which their names contain ISCREAM:
jstack -l 38515 | mjprof contains/name,ISCREAM/
Sort them by state
cat mystack.txt | mjprof contains/name,ISCREAM/.sort/state/
Eliminate the Locked Ownable Synchronizers Section
jstack -l 38515 | mjprof contains/name,ISCREAM/.sort/state/.eliminate/los/
Shorten stack traces to include only 10 last stack frames
mjprof jstack/MyAppMainClass/.contains/name,ISCREAM/.sort/state/.eliminate/los/.keeptop/10/
Count threads
mjprof jstack/38515/.contains/name,ISCREAM/.sort/state/.eliminate/los/.keeptop/10/.count

Java API

mjprof-core is usable as a library. Add the dependency:

<dependency>
    <groupId>com.performizeit</groupId>
    <artifactId>mjprof-core</artifactId>
    <version>1.1.0</version>
</dependency>

Not on Maven Central yet. There is no publishing configuration for mjprof-core, so the snippet above will not resolve from a fresh checkout. Until it is published, build and install it into your local ~/.m2 repository yourself:

git clone https://github.com/AdoptOpenJDK/mjprof.git
cd mjprof
mvn -pl mjprof-core -am install

Stages are plugin instances, so their constructor arguments are typed and checked by the compiler:

List<ThreadInfo> runnable = Pipeline.fromFile("dump.txt")
    .contains(Attr.STATE, "RUNNABLE")
    .sort(Attr.NAME)
    .threads();

Anything without a convenience method — including plugins from your own jars — goes through map:

Pipeline.fromStdin()
    .map(new MergedCallees("java.net.SocketInputStream.read"))
    .collect();

Sample on your own schedule and merge later:

List<ThreadDump> samples = new ArrayList<>();
samples.add(Pipeline.fromJmxWithCpu("localhost:9010", 1, 0, null, null).collectOne());
// ... more samples, on your cadence ...

List<ThreadInfo> profile = Pipeline.fromDumps(samples)
    .merge(Attr.TID)        // sums cpu_ns, merges stack trees
    .withoutNameSuffix()
    .group(Attr.NAME)
    .sortDesc(Attr.CPU_NS)
    .threads();

Notes:

  • Collection runs on the calling thread. A source configured for several samples blocks for its own sleep interval.
  • Failures throw MjprofException rather than yielding an empty result.
  • group(Attr.STACK) and merge(Attr.STACK) are rejected: stack profiles have no value equality. Use merge(Attr.TID) then tree().
  • Dumps written as jstack text keep count, cpu_ns, wall_ms and %cpu. But JMX-sourced dumps do not re-split when concatenated — their header is not date-prefixed — so several JMX samples in one file read back as one dump. Use fromDumps for self-collected samples.
  • The expression DSL, macros and the supported_monads.txt plugin registry live in mjprof-cli, not in the library.

Building MJProf

mjprof uses maven for compilation. In order to build mjprof use the following command line:
mvn clean package This builds both modules and produces a self-contained runnable jar at mjprof-cli/target/mjprof-cli-1.1.0-jar-with-dependencies.jar. Run it with java -jar mjprof-cli/target/mjprof-cli-1.1.0-jar-with-dependencies.jar or via the launcher script in mjprof-cli/src/main/scripts/mjprof. A native binary (via GraalVM, mvn -Pnative package) is named mjprof.

Writing a plugin

mjprof monadic capabilities can be extended with plugins. If you feel something is missing you can write your own plugin. We will appreciate if you will contribute it back to the community. A new plugin belongs in mjprof-core (the plugin implementations live there; mjprof-cli only discovers and wires them up). A plugin is a Java class annotated with com.performizeit.mjprof.core.api.Plugin. The annotation requires "name" (the plugin name, must be unique), "category" (a PluginCategory value identifying which plugin-type interface it implements), and "params" (an array of @Param, describing the expected arguments); "description" is optional and used for the synopsis. For example:

import com.performizeit.mjprof.core.api.Param;
import com.performizeit.mjprof.core.api.Plugin;
import com.performizeit.mjprof.core.api.PluginCategory;

@Plugin(name = "group", params = {@Param(type = String.class)},
    category = PluginCategory.DUMP_REDUCER, description = "Group by an attribute")

No registration code is needed beyond the annotation: the build's process-classes phase regenerates supported_monads.txt from every annotated class it finds on the classpath.

There are several plugin types mappers filters terminals etc... for each type you will need to implement a different interface.

Mapper

Implement JStackMapper interface which includes a single method ThreadInfo map(ThreadInfo stck)

Filter

Implement JStackFilter interface which includes a single method boolean filter(ThreadIndo stck)

Terminal

Implement JStackFilter interface which includes a single method void addStackDump(JStackDump jsd)

Comparator

Implement JStackComparator interface which includes a single method int compare(ThreadInfo o1, ThreadInfo o2)

Installing a plugin

In order to install the plugin just drop your jar which contains plugin implementation into the 'plugins' directory inside mjprof installation directory.

Claude Code plugins

This repo ships a project .claude/settings.json that declares the caveman, ponytail, and superpowers Claude Code marketplaces and enables their plugins. Enabling does not install: external plugins only load after each user installs them once. In the repo, run:

/plugin install ponytail@ponytail
/plugin install caveman@caveman
/plugin install superpowers@superpowers-marketplace

Then /reload-plugins (or restart Claude Code). After that the committed settings keep the plugins enabled automatically, so the install is a one-time step per user.

About

A monadic java profiler

Resources

Stars

107 stars

Watchers

18 watching

Forks

Releases

Packages

Used by

Contributors

Languages