├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .travis.yml ├── COPYING.txt ├── Dockerfile ├── LICENSE.txt ├── README.md ├── examplePlot.png ├── jHiccup ├── jHiccupLogProcessor ├── jHiccupPlotter.xls ├── pom.xml └── src ├── main ├── assembly │ └── dist.xml └── java │ └── org │ └── jhiccup │ ├── HiccupMeter.java │ ├── HiccupMeterAttacher.java │ ├── Idle.java │ └── Version.java.template └── test └── java └── org └── jhiccup ├── HiccupConfigurationTest.java └── HiccupMeterTest.java /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Maven 17 | run: mvn package -B --file pom.xml 18 | 19 | test: 20 | 21 | runs-on: ${{ matrix.os }} 22 | strategy: 23 | matrix: 24 | os: [ubuntu-18.04, macOS-latest, windows-2016] 25 | # test on both latest and specific update of each major version: 26 | java: [7, 7.0.181, 8, 8.0.192, 9, 10, 11, 11.0.3, 12, 13, 13.0.4, 14, 15, 16, 17, 18-ea] 27 | fail-fast: false 28 | max-parallel: 5 29 | name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} 30 | 31 | steps: 32 | - uses: actions/checkout@v1 33 | - name: Set up JDK 34 | uses: actions/setup-java@v1 35 | with: 36 | java-version: ${{ matrix.java }} 37 | - name: Test with Maven 38 | run: mvn test -B --file pom.xml 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | out 4 | gh-pages 5 | jHiccup.iml 6 | jHiccup.jar 7 | .classpath 8 | .project 9 | .settings 10 | release.properties 11 | pom.xml.releaseBackup 12 | src/main/java/org/jhiccup/Version.java 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | git: 3 | depth: 3 4 | sudo: required 5 | services: 6 | - docker 7 | before_script: 8 | - docker build -t zulu-mvn-git . 9 | script: 10 | - docker run -v `pwd`:/jHiccup zulu-mvn-git /bin/sh -c "mvn -f /jHiccup/pom.xml test" -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM azul/zulu-openjdk:8 2 | 3 | RUN apt-get update 4 | RUN apt-get -qqy install maven 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | * This code was Written by Gil Tene of Azul Systems, and released to the 2 | * public domain, as explained at http://creativecommons.org/publicdomain/zero/1.0/ 3 | 4 | For users of this code who wish to consume it under the "BSD" license 5 | rather than under the public domain or CC0 contribution text mentioned 6 | above, the code found under this directory is *also* provided under the 7 | following license (commonly referred to as the BSD 2-Clause License). This 8 | license does not detract from the above stated release of the code into 9 | the public domain, and simply represents an additional license granted by 10 | the Author. 11 | 12 | ----------------------------------------------------------------------------- 13 | ** Beginning of "BSD 2-Clause License" text. ** 14 | 15 | Copyright (c) 2012, 2013, 2014, 2015, 2016 Gil Tene 16 | All rights reserved. 17 | 18 | Redistribution and use in source and binary forms, with or without 19 | modification, are permitted provided that the following conditions are met: 20 | 21 | 1. Redistributions of source code must retain the above copyright notice, 22 | this list of conditions and the following disclaimer. 23 | 24 | 2. Redistributions in binary form must reproduce the above copyright notice, 25 | this list of conditions and the following disclaimer in the documentation 26 | and/or other materials provided with the distribution. 27 | 28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 29 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 30 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 31 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 32 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 33 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 34 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 35 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 36 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 37 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 38 | THE POSSIBILITY OF SUCH DAMAGE. 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # jHiccup 4 | [![Build Status](https://travis-ci.org/giltene/jHiccup.svg?branch=master)](https://travis-ci.org/giltene/jHiccup) 5 | [![Java CI](https://github.com/giltene/jhiccup/workflows/Java%20CI/badge.svg)](https://github.com/giltene/jHiccup/actions) 6 | [![Gitter](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/giltene/jHiccup?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | ---------------------------------------------------------------------------- 9 | 10 | Written by Gil Tene of Azul Systems, and released to the public domain 11 | as explained at http://creativecommons.org/publicdomain/zero/1.0 12 | 13 | ---------------------------------------------------------------------------- 14 | 15 | Version: 2.0.10 16 | ---------------------------------------------------------------------------- 17 | 18 | jHiccup is a non-intrusive instrumentation tool that logs and records 19 | platform "hiccups" - including the JVM stalls that often happen when 20 | Java applications are executed and/or any OS or hardware platform noise 21 | that may cause the running application to not be continuously runnable. 22 | 23 | jHiccup can be executed in one of three main ways: 24 | 25 | 1. It can be run as a Java agent (using: `java -javaagent:jHiccup.jar`) 26 | 27 | 2. It can be injected into a running application (using: `jHiccup -p `) 28 | 29 | 3. It can also be run using a convenient wrapper command for your 30 | existing Java application (using: `jHiccup java myProg ...`) 31 | 32 | ---------------------------------------------------------------------------- 33 | 34 | ### Example jHiccup plot 35 | ![example plot] 36 | 37 | ---------------------------------------------------------------------------- 38 | # Using jHiccup as a Java agent: 39 | 40 | jHiccup is most often used as a Java agent. This is useful for platforms and 41 | environments where a Java agent is simpler to integrate into launch scripts, 42 | or in environments where using the bash jHiccup wrapper script is not practical 43 | (e.g. Windows, and environments where java is not directly launched from 44 | the command line). 45 | 46 | jHiccup.jar can be used as a Java agent using the following launch syntax: 47 | 48 | % java -javaagent:jHiccup.jar MyProgram 49 | 50 | or 51 | 52 | % java -javaagent:jHiccup.jar="" MyProgram.jar -a -b -c 53 | 54 | You can find the available options for the Java agent mode by running: 55 | 56 | % java -javaagent:jHiccup.jar="-h" 57 | 58 | Here is a Java agent usage example with explicit parameters: 59 | 60 | % java -javaagent:jHiccup.jar="-d 2000 -i 1000 -l hiccuplog -c" MyProgram.jar -a -b -c 61 | 62 | This example will record hiccups experienced during the running of `MyProgram.jar` 63 | in log file `hiccuplog`, while at the same time recording the hiccups experienced by 64 | a control process running in a separate JVM in the log file `c.hiccuplog`. 65 | Measurement will start 2 seconds after startup (rather than immediately), 66 | and interval data will be recorded every 1 second (rather than the default 5 seconds). 67 | 68 | Useful Java agent related notes: 69 | 70 | Note 1: When used as a java agent, jHiccup will treat spaces, commas, and 71 | semicolons as delimiting characters (`[ ,;]+`). For example, the option string 72 | `-d 0 -i 1000` is equivalent to the option string `-d,0,-i,1000`. This is 73 | useful for environments where placing space delimiters into quoted strings 74 | is difficult or confusing. 75 | 76 | Note 2: I find that a common way people add jHiccup as a Java agent is by using 77 | the `_JAVA_OPTIONS` environment variable. This often allows one to add the jHiccup 78 | Java agent without significant launch script surgery. For example: 79 | 80 | export _JAVA_OPTIONS='-javaagent:/path/to/jHiccup/target/jHiccup.jar="-d 20000 -i 1000"' 81 | 82 | ---------------------------------------------------------------------------- 83 | 84 | # Reading and processing the jHiccup log with jHiccupLogProcessor: 85 | 86 | jHiccup logs hiccup information in a histogram log (see 87 | [HdrHistogram.org](http://hdrhistogram.org/)). This histogram log contains a full, high fidelity 88 | histogram of all collected results in each interval, in a highly compressed 89 | form (typically using only ~200-400 bytes per interval). However, other than 90 | the timestamp and maximum hiccup magnitude found in the given interval, the 91 | rest of the log line for each interval is not human readable (it is a base64 92 | encoding of a compressed HdrHistogram). 93 | 94 | To translate the jHiccup log file to a more human-readable form, the jHiccupLogProcessor 95 | utility is provided. In it's simplest form, this utility can be used as such 96 | 97 | % jHiccupLogProcessor -i mylog.hlog -o mylog 98 | 99 | Which will produce log files `mylog` and `mylog.hgrm` containing a human readable 100 | interval log (with selected percentiles in each interval), as well as a human 101 | readable histogram percentile distribution log. 102 | 103 | jHiccupLogProcessor can also be used to produce log files for an arbitrary 104 | section of the jHiccup log, by using the optional `-start` and `-end` parameters. 105 | 106 | See `jHiccupLogProcessor -h` for more details. 107 | 108 | ---------------------------------------------------------------------------- 109 | 110 | # Hiccup Charts: Plotting jHiccup results 111 | 112 | Since jHiccup uses [HdrHistogram](http://hdrhistogram.org/) and produces 113 | HdrHistogram logs, various tools that plot and view histogram logs can be 114 | used to analyze jhiccup data. Some common tools include 115 | [HistggramLogAnalyzer](https://github.com/HdrHistogram/HistogramLogAnalyzer) 116 | , [HdrHistogramVisualizer](https://github.com/ennerf/HdrHistogramVisualizer) 117 | , and a javascript-based in-browser [histogram log parser](https://hdrhistogram.github.io/HdrHistogramJSDemo/logparser.html) 118 | 119 | ---------------------------------------------------------------------------- 120 | 121 | # Launching jHiccup by attaching it to existing, running application: 122 | 123 | The jHiccup agent can be injected into a live, running Java application 124 | if the environment supports the java attach API (which is typically available 125 | in java environments running Java SE 6 or later). 126 | 127 | $ jHiccup -p 128 | 129 | NOTE: In order to attach to a running java application, the running 130 | application needs to have `${JAVA_HOME}/lib/tools.jar` in it's classpath. 131 | While this is commonly the case already for many IDE and desktop environments, 132 | and for environments that involve or enable other attachable agents (such as 133 | profilers), you may find that it is not included in your application's 134 | classpath, and that it needs to be added if attaching jHiccup at runtime 135 | is needed (launching jHiccup as a Java agent per the below may be a good 136 | alternative). 137 | 138 | ---------------------------------------------------------------------------- 139 | 140 | # Running jHiccup using the Wrapper Script form: 141 | 142 | In the wrapper script form, all it takes is adding the word "jHiccup" in 143 | front of whatever the java invocation command line is. 144 | 145 | For example, if your program were normally executed as: 146 | 147 | java MyProgram -a -b -c 148 | 149 | The launch line would become: 150 | 151 | jHiccup java MyProgram -a -b -c 152 | 153 | or, for a program launched with: 154 | 155 | /usr/bin/java -jar MyProgram.jar -a -b -c 156 | 157 | The launch line would become: 158 | 159 | jHiccup /usr/bin/java -jar MyProgram.jar -a -b -c 160 | 161 | or, to override the defaults by making the recording start delay 60 seconds 162 | and log to hlog, it would become: 163 | 164 | jHiccup -d 60000 -l hlog /usr/bin/java -jar MyProgram.jar -a -b -c 165 | 166 | The jar file also includes a simple "Idle" class to facilitate sanity checks 167 | without an external program. Here is a simple sanity test example: jHiccup 168 | with a 4 sec delay on recording start, wrapping an Idle run that does nothing 169 | for 30 seconds and exits: 170 | 171 | % jHiccup -d 4000 /usr/bin/java org.jhiccup.Idle -t 30000 172 | 173 | [Run `jHiccup -h`, or see comment in jHiccup script for more details.] 174 | 175 | ---------------------------------------------------------------------------- 176 | 177 | # Supported/Tested platforms: 178 | 179 | The jHiccup command is expected to work and has been tested on the following 180 | platforms: 181 | - Various Linux flavors (Tested on RHEL/CentOS 5.x and 6.x) 182 | - Mac OS X (tested on Lion, 10.7) 183 | - Windows with a Cygwin environment installed (tested on Windows 7) 184 | - Solaris (tested on both SPARC and x86) 185 | 186 | jHiccup.jar is expected to work as a java agent and has been tested on the 187 | following platforms: 188 | - Various Linux flavors (Tested on RHEL/CentOS 5.x and 6.x) 189 | - Mac OS X (tested on Lion, 10.7) 190 | - Windows standard command shell (tested on Windows 7) 191 | - Solaris (tested on both SPARC and x86) 192 | 193 | If you use jHiccup on other operating systems and setups, please report back 194 | on your experience so that we can expand the list. 195 | 196 | ---------------------------------------------------------------------------- 197 | 198 | # Using a control process to concurrently record baseline idle load hiccups: 199 | 200 | It is often useful to compare the hiccup behavior experienced by a running 201 | application with a "control" hiccup level of an idle workload, running on 202 | the same system and at the same time as the observed application. To make 203 | such control measurement convenient, jHiccup supports a `-c` option that will 204 | launch a concurrently executing "control process" and will separately log 205 | hiccup information of an idle workload running on a separate jvm for the 206 | duration of the instrumented application run. When selected, the control 207 | process log file name will match those used for the launching application, 208 | followed with a `.c`. 209 | 210 | For example: 211 | 212 | % jHiccup -l mylog -c /usr/bin/java -jar MyProgram.jar -a -b -c 213 | 214 | Will produce log file `mylog` detailing the hiccup behavior during the 215 | execution of `MyProgram.jar`, as well as a log file `c.mylog` detailing 216 | the hiccup behavior of an idle workload running on a separate jvm at 217 | the same time. 218 | 219 | ---------------------------------------------------------------------------- 220 | 221 | # Log file name recognizes and fills in %pid , %date , and %host terms 222 | 223 | When a log file name is specified with the `-l` option, the terms `%pid`, 224 | `%date`, and `%host` will be filled in with the appropriate information. 225 | The default log file name setting is simply `hiccup.%date.%pid`. 226 | 227 | ---------------------------------------------------------------------------- 228 | 229 | # Using jHiccup to process latency log files: 230 | 231 | jHiccup's main HiccupMeter class supports a mode `-f` that will take latency 232 | input from a file instead of recording it. This is useful for producing 233 | jHiccup-style text and graphical output for recorded latency data collected 234 | by some other means. 235 | 236 | When provided to the `-f` option, an input file is expected to contain two 237 | white-space delimited values per line (in either integer or real number format), 238 | representing a time stamp and a measured latency, both in millisecond units. 239 | 240 | It's important to note that the default "expected interval between samples" 241 | resolution in jHiccup and HiccupMeter is 1 millisecond. When processing 242 | input files, it is imperative that an appropriate value be supplied to 243 | the `-r` option, and that this value correctly represent the expected interval 244 | between samples in the provided input file. HiccupMeter will use this 245 | parameter to determine whether additional, artificial values should be added 246 | to the histogram recording, between input samples that are farther apart in 247 | time than the expected interval specified to the `-r` option. This behavior 248 | corrects for "coordinated omission" situations (where long response times 249 | lead to "skipped" requests that would have typically correlated with "bad" 250 | response times). A "large" value (e.g. `-r 100000`) can easily be specified 251 | to avoid any correction of this situation. 252 | 253 | Example: 254 | 255 | % java -jar jHiccup.jar -i 1000 -f inputLatenies -l latencies.hlog 256 | 257 | ---------------------------------------------------------------------------- 258 | 259 | # Using jHiccup to process pause logs from e.g. gc log files: 260 | 261 | When run in the file injection mode (`-f`), jHiccup's main HiccupMeter 262 | class supports an optional "fill zeros" (`-fz`) mode. This mode is 263 | useful for processing input that represent pause events rather than 264 | latencies. 265 | 266 | A common use case for this feature is producing hiccup logs from GC logs. 267 | GC logs will generally include pause information, which can be parsed out 268 | to a "pauses log". jHiccup can takes a "pauses logs" as input 269 | 270 | 271 | When provided to the `-f` option, in conjunction with a `-fz` option, an 272 | input file is expected to contain two white-space delimited values per 273 | line (in either integer or real number format), representing a time stamp 274 | and a measured length of a pause, both in millisecond units. 275 | 276 | Example (parsing gc log with +PrintGCTimeStamps): 277 | 278 | % java ... -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCTimeStamps -Xloggc:gc.log ... 279 | 280 | % awk -F": " '/Total time for which application threads were stopped/ {printf "%4.0f %4.3f\n", $1*1000.0, $3*1000.0;}' gc.log > gcPauses.log 281 | 282 | % java -jar jHiccup.jar -i 1000 -f gcPauses.log -fz -l pauses.hlog 283 | 284 | Example (with both +PrintGCTimeStamps and +PrintGCDateStamps): 285 | 286 | % java ... -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:gc.log ... 287 | 288 | % awk -F": " '/Total time for which application threads were stopped/ {printf "%4.0f %4.3f\n", $2*1000.0, $4*1000.0;}' gc.log > gcPauses.log 289 | 290 | % java -jar jHiccup.jar -i 1000 -f gcPauses.log -fz -l pauses.hlog 291 | 292 | 293 | ---------------------------------------------------------------------------- 294 | 295 | # Example: adding jHiccup to Tomcat runs: 296 | 297 | In Tomcat's `catalina.sh` script, replace the following line: 298 | 299 | exec "$_RUNJAVA" "$LOGGING_CONFIG" $JAVA_OPTS $CATALINA_OPTS 300 | 301 | with: 302 | 303 | exec "$JHICCUP_HOME/jHiccup" "$_RUNJAVA" "$LOGGING_CONFIG" $JAVA_OPTS $CATALINA_OPTS 304 | 305 | ---------------------------------------------------------------------------- 306 | 307 | # Note: Use of HdrHistogram. 308 | 309 | jHiccup depends on and makes systemic use of HdrHistogram to collected and 310 | report on the statistical distribution of hiccups. HdrHistogram sources 311 | and documentation can be found on GitHub, at 312 | http://hdrhistogram.github.io/HdrHistogram/ 313 | 314 | ---------------------------------------------------------------------------- 315 | 316 | # Building jHiccup: 317 | 318 | jHiccup can be (re)built from source files using Maven: 319 | 320 | % mvn package 321 | 322 | [example plot]:https://raw.github.com/giltene/jHiccup/master/examplePlot.png "Example jHiccup plot" 323 | -------------------------------------------------------------------------------- /examplePlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giltene/jHiccup/d2e18b8e9f42c60e67633bf7fc00b91cd321136b/examplePlot.png -------------------------------------------------------------------------------- /jHiccup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # jHiccup 4 | # 5 | # Written by Gil Tene, and released to the public domain, as explained at 6 | # http://creativecommons.org/publicdomain/zero/1.0/ 7 | # 8 | JHICCUP_Version=2.0.10 9 | # 10 | # jHiccup is a platform pause measurement tool, it is meant to observe the 11 | # underlying platform (JVM, OS, HW, etc.) responsiveness while under an 12 | # unrelated application load, and establish a lower bound for the stalls 13 | # the application would experience. It is run as a wrapper around 14 | # other applications so that measurements can be done without any changes 15 | # to application code. 16 | # 17 | # The purpose of jHiccup is to aid application operators and testers in 18 | # characterizing the inherent "platform hiccups" (execution stalls) 19 | # that a Java platform will display when running under load. The hiccups 20 | # measured are NOT stalls caused by the application's code. They are stalls 21 | # caused by the platform (JVM, OS, HW, etc.) that would be visible to and 22 | # affect any application thread running on the platform at the time of the 23 | # stall.It is generally safe to assume that if jHiccup experiences and 24 | # records a certain level of measured platform hiccups, the application 25 | # running on the same JVM platform during that time had experienced 26 | # hiccup/stall effects that are at least as large as the measured level. 27 | # 28 | # jHiccup's measurement works on the simple basis of measuring the time it 29 | # takes an effectively empty workload to perform work (while running alongside 30 | # whatever load the platform is carrying). Hiccup measurements are performed 31 | # by a thread that repeatedly sleeps for a given interval (-r for resolutionMs, 32 | # defaults to 1 msec), and logs the amount of time it took to actually wake up 33 | # each time in a detailed internal hiccup histogram. The assumption is that 34 | # if the measuring thread experienced some delay in waking up, any other thread 35 | # in the system could/would have experienced a similar delay, resulting in 36 | # application stalls. 37 | # 38 | # jHiccup produces a single log file (hiccup.YYMMDD.HHMM.pid.hlog) in Histogram 39 | # log format (see HdrHistogram documentaton for HistogramLogReader for details). 40 | # This log file captures histograms for each logging interval (set 41 | # with -i , defaults to 5000 msec)., which can later 42 | # be processed to reconstruct hiccup behavior over an arbitrary portion of the 43 | # log. The -l can be used to override the log file name. 44 | # 45 | # An associated utlity, jHiccupLogProcessor, generates two log files from this 46 | # log file: a sequential interval log file and a histogram log file. 47 | # The sequential interval log file logs a single %'ile stats line for each 48 | # reporting interval. The histogram log file includes a detailed %'ile histogram 49 | # of the run so far. 50 | # See documentation or help for jHiccupLogProcessor for details. 51 | # 52 | # 53 | # jHiccup can be configured to delay the start of measurement 54 | # (using the -d flag, defaults to 0 msec). 55 | # 56 | # jHiccup will continue to run until the application it is wrapping exists. 57 | # 58 | # Using the -c option (off by default), jHiccup can be configured to launch 59 | # a concurrently executing "control process" that will separately log hiccup 60 | # information of an idle workload running on a separate jvm for the duration 61 | # of the instrumented application run. When selected, the control process log 62 | # file name will match the one used for the preceded application, preceded 63 | # with a "c.". 64 | # 65 | # For convenience in testing, jHiccup can be executed as a simple wrapper for 66 | # java program execution. All it takes is adding the word "jHiccup" in front 67 | # of whatever the java invocation command line is. 68 | # 69 | # For example, if your program were normally executed as: 70 | # 71 | # java UsefulProgram -a -b -c 72 | # 73 | # The launch line would become: 74 | # 75 | # jHiccup java UsefulProgram -a -b -c 76 | # 77 | # or, for a program that is launch like this: 78 | # 79 | # /usr/bin/java -jar UsefulProgram.jar -a -b -c 80 | # 81 | # The launch line would become: 82 | # 83 | # jHiccup /usr/bin/java -jar UsefulProgram.jar -a -b -c 84 | # 85 | # or, to override the defaults by making the recording start delay 60 seconds 86 | # and log to hlog, it would become: 87 | # 88 | # jHiccup -d 60000 -l hlog /usr/bin/java -jar UsefulProgram.jar -a -b -c 89 | # 90 | 91 | # Figure out installed path: 92 | # On Linux, we'd do the following: 93 | # PARSED_SCRIPT=`readlink -f $0` 94 | # INSTALLED_PATH=`dirname $PARSED_SCRIPT` 95 | # But readlink -f doesn't work the same everywhere (e.g. Mac OS). We use this instead: 96 | function readlink_f () { _=`pwd`; cd `dirname $1` && echo `pwd` && cd $_; } 97 | INSTALLED_PATH=$(readlink_f $0) 98 | 99 | # Check if running from unpacked distribution archive by assuming jHiccup.jar 100 | # in the same directory as this script. If not, try to search in target/ directory 101 | # (running from the source repository build). 102 | JHICCUP_JAR_FILE=$INSTALLED_PATH/jHiccup.jar 103 | if [ ! -f $JHICCUP_JAR_FILE ] ; then 104 | JHICCUP_JAR_FILE=$INSTALLED_PATH/target/jHiccup.jar 105 | fi 106 | 107 | DATE=`date +%y%m%d.%H%M` 108 | 109 | # 110 | # Parse original java execution arguments: 111 | # 112 | count=0 113 | JHiccupArgs= 114 | readingJHiccupArgs=0 115 | PARSED_BinJava= 116 | readingJavaBin=1 117 | readingJavaArgs=0 118 | PARSED_JavaArgs= 119 | PARSED_AppArgs= 120 | for var in $@; do 121 | # echo $count: "$var" 122 | if [ $readingJavaBin -eq 1 ] ; then 123 | # Looking for JavaBin. Identify and parse jHiccup args 124 | if [ $readingJHiccupArgs -eq 1 ]; then 125 | # This was marked as an arg to jHiccup 126 | JHiccupArgs="$JHiccupArgs $var" 127 | readingJHiccupArgs=0 128 | elif [ $var = "-v" ]; then 129 | # -v is a flag arg to jHiccup 130 | JHiccupArgs="$JHiccupArgs $var" 131 | elif [ $var = "-0" ]; then 132 | # -0 is a flag arg to jHiccup 133 | JHiccupArgs="$JHiccupArgs $var" 134 | elif [ $var = "-c" ]; then 135 | # -c is a flag arg to jHiccup 136 | JHiccupArgs="$JHiccupArgs $var" 137 | elif [ $var = "-o" ]; then 138 | # -o is a flag arg to jHiccup 139 | JHiccupArgs="$JHiccupArgs $var" 140 | elif [ ${var:0:1} = "-" ]; then 141 | # This is a parameter arg to jHiccup 142 | JHiccupArgs="$JHiccupArgs $var" 143 | readingJHiccupArgs=1 144 | else 145 | # Found JavaBin 146 | PARSED_BinJava="$var" 147 | readingJavaBin=0 148 | readingJavaArgs=1 149 | fi 150 | elif [ $readingJavaArgs -eq 1 ]; then 151 | # Parsing Java args 152 | if [ ${var:0:1} = "-" ]; then 153 | PARSED_JavaArgs="$PARSED_JavaArgs $var" 154 | else 155 | readingJavaArgs=0 156 | PARSED_AppArgs="$var" 157 | fi 158 | else 159 | # Parsing app args 160 | PARSED_AppArgs="$PARSED_AppArgs $var" 161 | fi 162 | let "count = $count + 1" 163 | done 164 | # At this point, we should have valid $PARSED_BinJava, $PARSED_JavaArgs, $PARSED_AppArgs: 165 | #echo PARSED_BinJava = "$PARSED_BinJava" 166 | #echo PARSED_JavaArgs = "$PARSED_JavaArgs" 167 | #echo PARSED_AppArgs = "$PARSED_AppArgs" 168 | 169 | # 170 | # Parse jHiccup arguments: 171 | # 172 | JHICCUP_DelayArg= 173 | readingDelayArg=0 174 | JHICCUP_RunTimeArg= 175 | readingRunTimeArg=0 176 | JHICCUP_IntervalArg= 177 | readingIntervalArg=0 178 | JHICCUP_ResolutionArg= 179 | readingResolutionArg=0 180 | JHICCUP_LognameArg= 181 | readingLognnameArg=0 182 | JHICCUP_PidOfProcessToAttacheToArg= 183 | readingPidOfProcessToAttacheToArg=0 184 | 185 | verboseOutput= 186 | logFormatCsv= 187 | startTimeAtZero= 188 | JHiccupArgs_parse_error= 189 | JHICCUP_ControlProcessFlag= 190 | 191 | for var in $JHiccupArgs; do 192 | if [ $readingDelayArg -eq 1 ]; then 193 | JHICCUP_DelayArg=$var 194 | readingDelayArg=0 195 | elif [ $readingRunTimeArg -eq 1 ]; then 196 | JHICCUP_RunTimeArg=$var 197 | readingRunTimeArg=0 198 | elif [ $readingIntervalArg -eq 1 ]; then 199 | JHICCUP_IntervalArg=$var 200 | readingIntervalArg=0 201 | elif [ $readingResolutionArg -eq 1 ]; then 202 | JHICCUP_ResolutionArg=$var 203 | readingResolutionArg=0 204 | elif [ $readingLognnameArg -eq 1 ]; then 205 | JHICCUP_LognameArg=$var 206 | readingLognnameArg=0 207 | elif [ $readingPidOfProcessToAttacheToArg -eq 1 ]; then 208 | JHICCUP_PidOfProcessToAttacheToArg=$var 209 | readingPidOfProcessToAttacheToArg=0 210 | elif [ $var = "-d" ]; then 211 | readingDelayArg=1 212 | elif [ $var = "-t" ]; then 213 | readingRunTimeArg=1 214 | elif [ $var = "-i" ]; then 215 | readingIntervalArg=1 216 | elif [ $var = "-r" ]; then 217 | readingResolutionArg=1 218 | elif [ $var = "-l" ]; then 219 | readingLognnameArg=1 220 | elif [ $var = "-p" ]; then 221 | readingPidOfProcessToAttacheToArg=1 222 | elif [ $var = "-c" ]; then 223 | JHICCUP_ControlProcessFlag=1 224 | elif [ $var = "-0" ]; then 225 | startTimeAtZero=1 226 | elif [ $var = "-o" ]; then 227 | logFormatCsv=1 228 | elif [ $var = "-v" ]; then 229 | verboseOutput=1 230 | echo jHiccup version $JHICCUP_Version 231 | else 232 | JHiccupArgs_parse_error=1 233 | fi 234 | done 235 | 236 | if [ $readingDelayArg -eq 1 ]; then 237 | JHiccupArgs_parse_error=1 238 | elif [ $readingRunTimeArg -eq 1 ]; then 239 | JHiccupArgs_parse_error=1 240 | elif [ $readingIntervalArg -eq 1 ]; then 241 | JHiccupArgs_parse_error=1 242 | elif [ $readingResolutionArg -eq 1 ]; then 243 | JHiccupArgs_parse_error=1 244 | elif [ $readingLognnameArg -eq 1 ]; then 245 | JHiccupArgs_parse_error=1 246 | elif [ $readingPidOfProcessToAttacheToArg -eq 1 ]; then 247 | JHiccupArgs_parse_error=1 248 | fi 249 | 250 | # Should not have both a java command and a -p option: 251 | if [ $PARSED_BinJava ]; then 252 | if [ $JHICCUP_PidOfProcessToAttacheToArg ]; then 253 | JHiccupArgs_parse_error=1 254 | fi 255 | fi 256 | 257 | if [ $JHiccupArgs_parse_error ]; then 258 | echo $PARSED_SCRIPT $@ 259 | echo jHiccup version $JHICCUP_Version 260 | echo "Usage:" 261 | echo " jHiccup [-d startupDelayMsec] [-t runTimeMsec] [-i recordingIntervalMsec] [-l logname]" 262 | echo " [-r sampleResolutionMsec] [-c] [-p pidOfProcessToAttachTo]" 263 | echo " or:" 264 | echo " jHiccup [-d startupDelayMsec] [-t runTimeMsec] [-i recordingIntervalMsec] [-l logname]" 265 | echo " [-r sampleResolutionMsec] [-c] " 266 | echo "Where:" 267 | echo " -l logname Sets the log files to and .hgrm" 268 | echo " (default is \"hiccup.yymmdd.hhmm.pid\") " 269 | echo " (replaces occurrences of %pid and %date with appropriate info)" 270 | echo " -o Output log files in CSV format" 271 | echo " -c Concurrently start a control process to record hiccups" 272 | echo " experienced by an Idle load running on a separate jvm" 273 | echo " in log files .c and .c.hgrm" 274 | echo " -p pidOfProcessToAttachTo Attach to the process with given pid and inject jHiccup as an agent" 275 | echo " (no default)" 276 | echo " -d startupDelayMsec Sets the delay, in milliseconds before sampling starts" 277 | echo " (default 0)" 278 | echo " -t runTimeMsec Limit measurement and logging time" 279 | echo " (default 0, for infinite)" 280 | echo " -0 Start logfile timestamps at 0 (as opposed to JVM uptime at start point)" 281 | echo " (default off)" 282 | echo " -i recordingIntervalMsec Sets the reporting interval in milliseconds" 283 | echo " (default 5000)" 284 | echo " -r sampleResolutionMsec Sets the sampling resolution in milliseconds" 285 | echo " (default 1)" 286 | echo " -v Verbose output" 287 | exit -1 288 | fi 289 | 290 | JHICCUP_Options="" 291 | 292 | if [ $JHICCUP_DelayArg ]; then 293 | JHICCUP_Options="${JHICCUP_Options}-d $JHICCUP_DelayArg " 294 | fi 295 | 296 | if [ $JHICCUP_RunTimeArg ]; then 297 | JHICCUP_Options="${JHICCUP_Options}-t $JHICCUP_RunTimeArg " 298 | fi 299 | 300 | if [ $JHICCUP_IntervalArg ]; then 301 | JHICCUP_Options="${JHICCUP_Options}-i $JHICCUP_IntervalArg " 302 | fi 303 | 304 | if [ $JHICCUP_ResolutionArg ]; then 305 | JHICCUP_Options="${JHICCUP_Options}-r $JHICCUP_ResolutionArg " 306 | fi 307 | 308 | if [ $JHICCUP_LognameArg ]; then 309 | JHICCUP_Options="${JHICCUP_Options}-l $JHICCUP_LognameArg " 310 | fi 311 | 312 | if [ $startTimeAtZero ]; then 313 | JHICCUP_Options="${JHICCUP_Options}-0 " 314 | fi 315 | 316 | if [ $logFormatCsv ]; then 317 | JHICCUP_Options="${JHICCUP_Options}-o " 318 | fi 319 | 320 | if [ $JHICCUP_ControlProcessFlag ]; then 321 | JHICCUP_Options="${JHICCUP_Options}-c " 322 | fi 323 | 324 | if [ $verboseOutput ]; then 325 | JHICCUP_Options="${JHICCUP_Options}-v " 326 | fi 327 | 328 | # Deal with Windows/cygwin path normalization syntax needs: 329 | # Key Assumption: only cygwin/Windows installations will have a cygpath command... 330 | cygpath -w $JHICCUP_JAR_FILE &> /dev/null 331 | if [ $? -eq 0 ] ; then 332 | # if using cygwin, use valid windows-style classpath 333 | JHICCUP_JAR_FILE=`cygpath -w $JHICCUP_JAR_FILE` 334 | echo Windows path for hiccup jar file is $JHICCUP_JAR_FILE 335 | fi 336 | 337 | if [ $JHICCUP_PidOfProcessToAttacheToArg ]; then 338 | JHICCUP_Options="${JHICCUP_Options}-j $JHICCUP_JAR_FILE -p $JHICCUP_PidOfProcessToAttacheToArg " 339 | 340 | # 341 | # Prepare and execute attach command: 342 | # 343 | 344 | CMD="$JAVA_HOME/bin/java -cp $JAVA_HOME/lib/tools.jar:$JHICCUP_JAR_FILE org.jhiccup.HiccupMeterAttacher $JHICCUP_Options" 345 | if [ $verboseOutput ]; then 346 | echo jHiccup executing: $CMD 347 | fi 348 | exec $JAVA_HOME/bin/java -cp $JAVA_HOME/lib/tools.jar:$JHICCUP_JAR_FILE org.jhiccup.HiccupMeterAttacher $JHICCUP_Options 349 | fi 350 | 351 | # 352 | # Prepare and execute command: 353 | # 354 | CMD="$PARSED_BinJava -javaagent:$JHICCUP_JAR_FILE=\"$JHICCUP_Options\" $PARSED_JavaArgs $PARSED_AppArgs" 355 | if [ $verboseOutput ]; then 356 | echo jHiccup executing: $CMD 357 | fi 358 | exec $PARSED_BinJava -javaagent:$JHICCUP_JAR_FILE="$JHICCUP_Options" $PARSED_JavaArgs $PARSED_AppArgs 359 | #exec $CMD 360 | -------------------------------------------------------------------------------- /jHiccupLogProcessor: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # jHiccupLogProcessor 4 | # 5 | # Written by Gil Tene, and released to the public domain, as explained at 6 | # http://creativecommons.org/publicdomain/zero/1.0/ 7 | # 8 | JHICCUP_Version=2.0.4 9 | # 10 | # jHiccupLogProcessor will process an input log and can generate two 11 | # different log files from a single jHiccup histogram log file: a 12 | # sequential interval log file and a histogram log file. 13 | # 14 | # The sequential interval log file logs a single %'ile stats line for 15 | # each reporting interval. 16 | # 17 | # The histogram percentile log file includes a detailed %'ile histogram 18 | # of the entire log file range. 19 | # 20 | # jHiccupLogProcessor will process an input log file when provided with 21 | # the -i option. When no -i option is provided, standard input 22 | # will be processed. 23 | # 24 | # When provided with an output file name with the -o option 25 | # (e.g. "-o mylog"), jHiccupLogProcessor will produce both output files 26 | # under the names and .hgrm (e.g. mylog and mylog.hgrm). 27 | # 28 | # When not provided with an output file name, jHiccupLogProcessor will 29 | # produce [only] the histogram percentile log output to standard output. 30 | # 31 | # jHiccupLogProcessor accepts optional -start and -end time range 32 | # parameters. When provided, the output will only reflect the portion 33 | # of the input log with timestamps that fall within the provided start 34 | # and end time range parameters. 35 | # 36 | # jHiccupLogProcessor also accepts and optional -csv parameter, which 37 | # will cause the output formatting (of both output file forms) to use 38 | # a CSV file format. 39 | # 40 | 41 | # Figure out installed path: 42 | # On Linux, we'd do the following: 43 | # PARSED_SCRIPT=`readlink -f $0` 44 | # INSTALLED_PATH=`dirname $PARSED_SCRIPT` 45 | # But readlink -f doesn't work the same everywhere (e.g. Mac OS). We use this instead: 46 | readlink_f() { 47 | local _dir _path="$1" 48 | [ -d "$_path" ] && _path=$(cd "$_path"; pwd -P) 49 | while [ -L "$_path" ]; do 50 | _dir=$(dirname "$_path") 51 | _path=$(readlink "$_path") 52 | [ -n "${_path##/*}" ] && _path="${_dir}/${_path}" 53 | done 54 | [ -f "$_path" ] && _path="$(cd "$(dirname "$_path")"; pwd -P)/${_path##*/}" 55 | echo "${_path%/}" 56 | } 57 | 58 | INSTALLED_PATH=$(dirname "$(readlink_f "$0")") 59 | 60 | # Check if running from unpacked distribution archive by assuming jHiccup.jar 61 | # in the same directory as this script. If not, try to search in target/ directory 62 | # (running from the source repository build). 63 | JHICCUP_JAR_FILE="$INSTALLED_PATH/jHiccup.jar" 64 | if [ ! -f "$JHICCUP_JAR_FILE" ] ; then 65 | JHICCUP_JAR_FILE="$INSTALLED_PATH/target/jHiccup.jar" 66 | fi 67 | 68 | if [ ! -f "$JHICCUP_JAR_FILE" ] ; then 69 | JHICCUP_JAR_FILE="$INSTALLED_PATH/../jHiccup.jar" 70 | fi 71 | 72 | if [ ! -f "$JHICCUP_JAR_FILE" ] ; then 73 | echo "For this command to run, jHiccup.jar must be available in '${INSTALLED_PATH%/bin*}'." 74 | exit 1 75 | fi 76 | 77 | JAVA_BIN=`which java` 78 | 79 | if [ $JAVA_HOME ]; then 80 | JAVA_CMD=$JAVA_HOME/bin/java 81 | elif [ $JAVA_BIN ]; then 82 | JAVA_CMD=$JAVA_BIN 83 | else 84 | echo "For this command to run, either $JAVA_HOME must be set, or java must be in the path." 85 | exit 1 86 | fi 87 | 88 | # 89 | # Parse original java execution arguments: 90 | # 91 | # At this point, we should have valid $PARSED_BinJava, $PARSED_JavaArgs, $PARSED_AppArgs: 92 | #echo PARSED_BinJava = "$PARSED_BinJava" 93 | #echo PARSED_JavaArgs = "$PARSED_JavaArgs" 94 | #echo PARSED_AppArgs = "$PARSED_AppArgs" 95 | 96 | # Deal with Windows/cygwin path normalization syntax needs: 97 | # Key Assumption: only cygwin/Windows installations will have a cygpath command... 98 | cygpath -w $JHICCUP_JAR_FILE &> /dev/null 99 | if [ $? -eq 0 ] ; then 100 | # if using cygwin, use valid windows-style classpath 101 | JHICCUP_JAR_FILE=`cygpath -w $JHICCUP_JAR_FILE` 102 | echo Windows path for hiccup jar file is $JHICCUP_JAR_FILE 103 | fi 104 | 105 | exec $JAVA_CMD -cp $JHICCUP_JAR_FILE org.jhiccup.internal.hdrhistogram.HistogramLogProcessor $@ 106 | #exec $CMD 107 | -------------------------------------------------------------------------------- /jHiccupPlotter.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giltene/jHiccup/d2e18b8e9f42c60e67633bf7fc00b91cd321136b/jHiccupPlotter.xls -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jhiccup 6 | jHiccup 7 | 2.0.11-SNAPSHOT 8 | 9 | jHiccup 10 | 11 | http://jhiccup.org 12 | 13 | 14 | jHiccup is a lightweight, non-intrusive instrumentation tool that logs and records 15 | platform "hiccups" - including the JVM stalls that often happen when 16 | Java applications are executed and/or any OS or hardware platform noise 17 | that may cause the running application to not be continuously runnable. 18 | 19 | 20 | 21 | 22 | 23 | * This code was Written by Gil Tene of Azul Systems, and released to the 24 | * public domain, as explained at http://creativecommons.org/publicdomain/zero/1.0/ 25 | 26 | Public Domain, per Creative Commons CC0 27 | http://creativecommons.org/publicdomain/zero/1.0/ 28 | 29 | 30 | 31 | 32 | 33 | giltene 34 | Gil Tene 35 | https://github.com/giltene 36 | 37 | 38 | 39 | 40 | scm:git:git://github.com/giltene/jHiccup.git 41 | scm:git:git://github.com/giltene/jHiccup.git 42 | scm:git:git@github.com:giltene/jHiccup.git 43 | jHiccup-2.0.10 44 | 45 | 46 | 47 | https://github.com/giltene/jHiccup/issues 48 | GitHub Issues 49 | 50 | 51 | jar 52 | 53 | 54 | UTF-8 55 | src/main/java/org/jhiccup/Version.java.template 56 | src/main/java/org/jhiccup/Version.java 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-compiler-plugin 65 | 2.3.2 66 | 67 | 1.7 68 | 1.7 69 | UTF-8 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-surefire-plugin 76 | 2.12.4 77 | 78 | false 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-dependency-plugin 85 | 86 | 87 | copy-dependencies 88 | prepare-package 89 | 90 | copy-dependencies 91 | 92 | 93 | ${project.build.directory}/lib 94 | false 95 | false 96 | true 97 | 98 | tools 99 | 100 | 101 | 102 | copy-installed 103 | install 104 | 105 | copy 106 | 107 | 108 | 109 | 110 | ${project.groupId} 111 | ${project.artifactId} 112 | ${project.version} 113 | ${project.packaging} 114 | jHiccup.jar 115 | 116 | 117 | ${project.basedir} 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-jar-plugin 126 | 2.4 127 | 128 | jHiccup 129 | 130 | 131 | false 132 | org.jhiccup.HiccupMeter 133 | true 134 | true 135 | 136 | 137 | org.jhiccup.HiccupMeter 138 | org.jhiccup.HiccupMeter 139 | 140 | 141 | 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-release-plugin 147 | 2.5 148 | 149 | 150 | 151 | 152 | org.sonatype.plugins 153 | jarjar-maven-plugin 154 | 1.9 155 | 156 | 157 | package 158 | 159 | jarjar 160 | 161 | 162 | 163 | org.hdrhistogram:HdrHistogram 164 | 165 | 166 | 167 | org.HdrHistogram.** 168 | org.jhiccup.internal.hdrhistogram.@1 169 | 170 | 171 | true 172 | 173 | 174 | 175 | 176 | 177 | 178 | com.google.code.maven-replacer-plugin 179 | maven-replacer-plugin 180 | 1.4.0 181 | 182 | 183 | process-sources 184 | 185 | replace 186 | 187 | 188 | 189 | 190 | ${version.template.file} 191 | ${version.file} 192 | 193 | 194 | \$BUILD_TIME\$ 195 | ${maven.build.timestamp} 196 | 197 | 198 | \$VERSION\$ 199 | ${project.version} 200 | 201 | 202 | 203 | 204 | 205 | 206 | maven-assembly-plugin 207 | 2.4 208 | 209 | 210 | src/main/assembly/dist.xml 211 | 212 | 213 | 214 | 215 | package 216 | 217 | single 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | junit 228 | junit 229 | 4.10 230 | test 231 | 232 | 233 | org.hdrhistogram 234 | HdrHistogram 235 | 2.1.12 236 | 237 | 238 | 239 | 240 | 241 | 242 | toolsjar-profile 243 | 244 | 245 | ${java.home}/../lib/tools.jar 246 | 247 | 248 | 249 | ${java.home}/../lib/tools.jar 250 | 251 | 252 | 253 | 254 | com.sun 255 | tools 256 | 257 | 99.9.9 258 | system 259 | ${java.home}/../lib/tools.jar 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /src/main/assembly/dist.xml: -------------------------------------------------------------------------------- 1 | 3 | dist 4 | 5 | tar.gz 6 | zip 7 | 8 | 9 | 10 | ${project.basedir} 11 | / 12 | 13 | COPYING.txt 14 | LICENSE.txt 15 | README.md 16 | jHiccupPlotter.xls 17 | pom.xml 18 | 19 | 20 | 21 | ${project.basedir} 22 | / 23 | 24 | jHiccup 25 | jHiccupLogProcessor 26 | 27 | 0744 28 | 29 | 30 | ${project.build.directory} 31 | / 32 | 33 | jHiccup.jar 34 | 35 | 36 | 37 | ${project.basedir}/src 38 | /src 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/org/jhiccup/HiccupMeter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Written by Gil Tene of Azul Systems, and released to the public domain, 3 | * as explained at http://creativecommons.org/publicdomain/zero/1.0/ 4 | * 5 | * @author Gil Tene 6 | */ 7 | 8 | package org.jhiccup; 9 | 10 | import org.HdrHistogram.*; 11 | 12 | import java.io.*; 13 | import java.net.InetAddress; 14 | import java.net.UnknownHostException; 15 | import java.security.CodeSource; 16 | import java.util.*; 17 | import java.text.SimpleDateFormat; 18 | import java.lang.management.*; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * HiccupMeter is a platform pause measurement tool, it is meant to observe 23 | * the underlying platform (JVM, OS, HW, etc.) responsiveness while under 24 | * an unrelated application load, and establish a lower bound for the stalls 25 | * the application would experience. It can be run as a wrapper around 26 | * other applications so that measurements can be done without any changes 27 | * to application code. 28 | *

29 | * The purpose of HiccupMeter is to aid application operators and testers 30 | * in characterizing the inherent "platform hiccups" (execution stalls) 31 | * that a Java platform will display when running under load. The hiccups 32 | * measured are NOT stalls caused by the application's code. They are stalls 33 | * caused by the platform (JVM, OS, HW, etc.) that would be visible to and 34 | * affect any application thread running on the platform at the time of the 35 | * stall. It is generally safe to assume that if HiccupMeter experiences and 36 | * records a certain level of measured platform hiccups, the application 37 | * running on the same JVM platform during that time had experienced 38 | * hiccup/stall effects that are at least as large as the measured level. 39 | *

40 | * HiccupMeter's measurement works on the simple basis of measuring the time 41 | * it takes an effectively empty workload to perform work (while running 42 | * alongside whatever load the platform is carrying). Hiccup measurements are 43 | * performed by a thread that repeatedly sleeps for a given interval (-r for 44 | * resolutionMs, defaults to 1 msec), and logs the amount of time it took to 45 | * actually wake up each time in a detailed internal hiccup histogram. The 46 | * assumption is that if the measuring thread experienced some delay in waking 47 | * up, any other thread in the system could/would have experienced a similar 48 | * delay, resulting in application stalls. 49 | *

50 | * HiccupMeter collects both raw and corrected (weighted) histogram results. 51 | * When the reported time in a histogram exceeds the interval used between 52 | * measurements, the raw histogram data would reflect only the single reported 53 | * results, while the corrected histogram data will reflect an appropriate 54 | * number of additional results with linearly decreasing times (down to a time 55 | * that is lower than the measurement interval). While raw and corrected 56 | * histogram data are both tracked internally, it is the corrected numbers that 57 | * are logged and reported, as they will more accurately reflect the response 58 | * time that a random, uncoordinated request would have experienced. 59 | *

60 | * HiccupMeter logs a single line with hiccup %'ile stats each reporting 61 | * interval (set with -i , defaults to 5000 msec) to a 62 | * log file. The log file name can be optionally controlled with the -l 63 | * flag, and will default (if no logfile name is supplied)to a name derived from 64 | * the process id and time (hiccup.yyMMdd.HHmm.pid). HiccupMeter will also produce 65 | * a detailed histogram log file under the .histogram name. A new 66 | * histogram log file will be produced each interval, and will replace the 67 | * one generated in the previous interval. 68 | *

69 | * HiccupMeter can be configured to delay the start of measurement 70 | * (using the -d flag, defaults to 0 msec). It can also be 71 | * configured to terminate measurement after a given length of time (using the 72 | * -t flag). If the -t flag is not used, HiccupMeter will continue 73 | * to run until the Class executed with via the -exec parameter (see below) 74 | * exits, or indefinitely (if no -exec is used). 75 | *

76 | * HiccupMeter can be configured to launch a separate "control process" on 77 | * startup (using the -c flag, defaulting to 78 | * nothing). This option launches a separate, standalone instance of 79 | * HiccupMeter wrapping running an idle workload, such that a concurrent 80 | * control measurement is established on the same system, at the same time 81 | * as the main observed application load is run. 82 | *

83 | * For convenience in testing, HiccupMeter is typically launched as a 84 | * javaagent. For example, if your program were normally executed as: 85 | *

86 | * java UsefulProgram -a -b -c 87 | *

88 | * This is how you would execute the same program so that HiccupMeter would 89 | * record hiccups while it is running: 90 | *

91 | * java -javaagent:jHiccup.jar UsefulProgram -a -b -c 92 | *

93 | * Common use example: 94 | *

95 | * Measure internal jitter of JVM running MyProg, log into logFile and 96 | * logFile.hgrm, report in 5 seconds intervals (which is the default), start 97 | * measurements 60 seconds into the run, and run concurrent control process 98 | * that will record hiccups on an idle workload running at the same time, 99 | * logging those into c.logFile and c.logFile.hgrm: 100 | *

101 | * java -javaagent:jHiccup.jar="-d 60000 -l testLog -c c.testlog" MyProg 102 | *

103 | * Note: while HiccupMeter is typically executed as a javaagent, it can be 104 | * run as a standalone program, in which case it will execute for the 105 | * duration of time specified with the -t option, or if the 106 | * -terminateOnStdIn flag is used, it will terminate execution when standard 107 | * input is severed. This last option is useful for executing HiccupMeter as 108 | * a standalone control process launched from a javaagent HiccupMeter 109 | * instance. 110 | * 111 | * Note: HiccupMeter can be used to process data from an input file instead 112 | * of sampling it. When the [-f inputFileName] option is used, the input file 113 | * is expected to contain two white-space delimited values per line (in either 114 | * integer or real number format), representing a time stamp and a measured 115 | * latency, both in millisecond units. It's important to note that the default 116 | * "expected interval between samples" resolution in jHiccup and HiccupMeter 117 | * is 1 millisecond. When processing input files, it is imperative that the 118 | * appropriate value be supplied to the -r option, and that this value correctly 119 | * represent the expected interval between samples in the provided input file. 120 | * HiccupMeter will use this parameter to determine whether additional, artificial 121 | * values should be added to the histogram recording, between input samples that 122 | * are farther apart in time than the expected interval specified to the -r option. 123 | * This behavior corrects for "coordinated omission" situations (where long response 124 | * times lead to "skipped" requests that would have typically correlated with "bad" 125 | * response times). A "large" value (e.g. -r 100000) can easily be specified to 126 | * avoid any correction of this situation. 127 | * 128 | * Note: HiccupMeter makes systemic use of HdrHistogram to collected and report 129 | * on the statistical distribution of hiccups. HdrHistogram sources, documentation, 130 | * and a ready to use jar file can all be found on GitHub, 131 | * at http://giltene.github.com/HdrHistogram 132 | */ 133 | 134 | 135 | public class HiccupMeter extends Thread { 136 | 137 | private static final String versionString = "jHiccup version " + Version.version; 138 | 139 | static final String defaultHiccupLogFileName = "hiccup.%date.%pid.hlog"; 140 | 141 | protected final PrintStream log; 142 | 143 | protected final HistogramLogWriter histogramLogWriter; 144 | 145 | protected final HiccupMeterConfiguration config; 146 | 147 | protected static class HiccupMeterConfiguration { 148 | public boolean terminateWithStdInput = false; 149 | public double resolutionMs = 1.0; 150 | public long runTimeMs = 0; 151 | public long reportingIntervalMs = 5000; 152 | public long startDelayMs = 0; 153 | public boolean startDelayMsExplicitlySpecified = false; 154 | 155 | public boolean verbose = false; 156 | public boolean allocateObjects = false; 157 | public String logFileName; 158 | public boolean logFileExplicitlySpecified = false; 159 | public String inputFileName = null; 160 | public boolean fillInZerosInInputFile = false; 161 | public boolean logFormatCsv = false; 162 | 163 | public boolean launchControlProcess = false; 164 | public long launchControlProcessHeapSizeMBFilter = 0; 165 | public String controlProcessLogFileName = null; 166 | public String controlProcessCommand = null; 167 | public boolean controlProcessJvmArgsExplicitlySpecified = false; 168 | public String controlProcessJvmArgs; 169 | 170 | public boolean attachToProcess = false; 171 | public String pidOfProcessToAttachTo = null; 172 | public String agentJarFileName = null; 173 | public String agentArgs = null; 174 | 175 | public boolean startTimeAtZero = false; 176 | 177 | public long lowestTrackableValue = 1000L * 20L; // default to ~20usec best-case resolution 178 | public long highestTrackableValue = 30 * 24 * 3600 * 1000L * 1000L * 1000L; // 1 Month 179 | public int numberOfSignificantValueDigits = 2; 180 | 181 | public boolean error = false; 182 | public String errorMessage = ""; 183 | 184 | String fillInPidAndDate(String logFileName) { 185 | final String processName = 186 | java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); 187 | final String processID = processName.split("@")[0]; 188 | final SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd.HHmm"); 189 | final String formattedDate = formatter.format(new Date()); 190 | 191 | 192 | logFileName = logFileName.replaceAll("%pid", processID); 193 | logFileName = logFileName.replaceAll("%date", formattedDate); 194 | 195 | try { 196 | // Extract host name from environment variables or IP address: 197 | String host = System.getenv("HOSTNAME"); 198 | if (host == null) { 199 | host = System.getenv("HOST"); 200 | } 201 | if (host == null) { 202 | host = System.getenv("COMPUTERNAME"); 203 | } 204 | if (host == null) { 205 | InetAddress ipAddr = InetAddress.getLocalHost(); 206 | host = ipAddr.getHostName(); 207 | } 208 | logFileName = logFileName.replaceAll("%host", host); 209 | } catch (UnknownHostException e) { 210 | } 211 | return logFileName; 212 | } 213 | 214 | public HiccupMeterConfiguration(final String[] args, String defaultLogFileName) { 215 | logFileName = defaultLogFileName; 216 | try { 217 | for (int i = 0; i < args.length; ++i) { 218 | if (args[i].equals("-v")) { 219 | verbose = true; 220 | } else if (args[i].equals("-0")) { 221 | startTimeAtZero = true; 222 | } else if (args[i].equals("-a")) { 223 | allocateObjects = true; 224 | } else if (args[i].equals("-p")) { 225 | attachToProcess = true; 226 | pidOfProcessToAttachTo = args[++i]; // lgtm [java/index-out-of-bounds] 227 | } else if (args[i].equals("-j")) { 228 | agentJarFileName = args[++i]; // lgtm [java/index-out-of-bounds] 229 | } else if (args[i].equals("-terminateWithStdInput")) { 230 | terminateWithStdInput = true; 231 | } else if (args[i].equals("-i")) { 232 | reportingIntervalMs = Long.parseLong(args[++i]); // lgtm [java/index-out-of-bounds] 233 | } else if (args[i].equals("-t")) { 234 | runTimeMs = Long.parseLong(args[++i]); // lgtm [java/index-out-of-bounds] 235 | } else if (args[i].equals("-d")) { 236 | startDelayMs = Long.parseLong(args[++i]); // lgtm [java/index-out-of-bounds] 237 | startDelayMsExplicitlySpecified = true; 238 | } else if (args[i].equals("-r")) { 239 | resolutionMs = Double.parseDouble(args[++i]); // lgtm [java/index-out-of-bounds] 240 | } else if (args[i].equals("-s")) { 241 | numberOfSignificantValueDigits = Integer.parseInt(args[++i]); // lgtm [java/index-out-of-bounds] 242 | } else if (args[i].equals("-l")) { 243 | logFileName = args[++i]; // lgtm [java/index-out-of-bounds] 244 | logFileExplicitlySpecified = true; 245 | } else if (args[i].equals("-f")) { 246 | inputFileName = args[++i]; // lgtm [java/index-out-of-bounds] 247 | lowestTrackableValue = 1L; // drop to ~1 nsec best-case resolution when processing files 248 | } else if (args[i].equals("-fz")) { 249 | fillInZerosInInputFile = true; 250 | } else if (args[i].equals("-c")) { 251 | launchControlProcess = true; 252 | } else if (args[i].equals("-cfmb")) { 253 | launchControlProcessHeapSizeMBFilter = Long.parseLong(args[++i]); // lgtm [java/index-out-of-bounds] 254 | } else if (args[i].equals("-x")) { 255 | controlProcessJvmArgs = args[++i]; // lgtm [java/index-out-of-bounds] 256 | controlProcessJvmArgsExplicitlySpecified = true; 257 | } else if (args[i].equals("-o")) { 258 | logFormatCsv = true; 259 | } else { 260 | throw new Exception("Invalid args: " + args[i]); 261 | } 262 | } 263 | 264 | logFileName = fillInPidAndDate(logFileName); 265 | 266 | if (attachToProcess) { 267 | if (!startDelayMsExplicitlySpecified) { 268 | startDelayMs = 0; 269 | } 270 | 271 | if (agentJarFileName == null) { 272 | throw new Exception("Invalid args, missing agent jar file name, specify with -j option"); 273 | } 274 | agentArgs = "-d " + startDelayMs + 275 | " -i " + reportingIntervalMs + 276 | ((startTimeAtZero) ? " -0" : "") + 277 | " -s " + numberOfSignificantValueDigits + 278 | " -r " + resolutionMs; 279 | 280 | if (runTimeMs != 0) { 281 | agentArgs += " -t " + runTimeMs; 282 | } 283 | 284 | if (logFileExplicitlySpecified) { 285 | agentArgs += " -l " + logFileName; 286 | } 287 | 288 | if (launchControlProcess) { 289 | agentArgs += " -c"; 290 | } 291 | 292 | if (controlProcessJvmArgsExplicitlySpecified) { 293 | agentArgs += " -x " + controlProcessJvmArgs; 294 | } 295 | 296 | if (verbose) { 297 | agentArgs += " -v"; 298 | } 299 | 300 | if (logFormatCsv) { 301 | agentArgs += " -o"; 302 | } 303 | } 304 | 305 | if (launchControlProcess && (launchControlProcessHeapSizeMBFilter > 0)) { 306 | MemoryMXBean mxbean = ManagementFactory.getMemoryMXBean(); 307 | MemoryUsage memoryUsage = mxbean.getHeapMemoryUsage(); 308 | long estimatedHeapMB = (memoryUsage.getMax() / (1024 * 1024)); 309 | if (estimatedHeapMB < launchControlProcessHeapSizeMBFilter) { 310 | launchControlProcess = false; 311 | } 312 | } 313 | 314 | if (launchControlProcess) { 315 | File filePath = new File(logFileName); 316 | String parentFileNamePart = filePath.getParent(); 317 | 318 | String childFileNamePart = filePath.getName(); 319 | // Derive control process log file name from logFileName: 320 | File controlFilePath = new File(parentFileNamePart, childFileNamePart + ".c"); 321 | controlProcessLogFileName = controlFilePath.getPath(); 322 | 323 | // Compute path to agent's JAR file 324 | CodeSource agentCodeSource = HiccupMeter.class.getProtectionDomain().getCodeSource(); 325 | String agentPath = new File(agentCodeSource.getLocation().toURI()).getPath(); 326 | 327 | // Derive controlProcessCommand from our java home, class name, and parsed 328 | // options: 329 | controlProcessCommand = 330 | System.getProperty("java.home") + 331 | File.separator + "bin" + File.separator + "java" + 332 | (controlProcessJvmArgsExplicitlySpecified ? " " + controlProcessJvmArgs : "") + 333 | " -cp " + agentPath + 334 | " -Dorg.jhiccup.avoidRecursion=true" + 335 | " " + HiccupMeter.class.getCanonicalName() + 336 | " -l " + controlProcessLogFileName + 337 | " -i " + reportingIntervalMs + 338 | " -d " + startDelayMs + 339 | ((startTimeAtZero) ? " -0" : "") + 340 | ((logFormatCsv) ? " -o" : "") + 341 | " -s " + numberOfSignificantValueDigits + 342 | " -r " + resolutionMs + 343 | " -terminateWithStdInput"; 344 | } 345 | if (resolutionMs < 0) { 346 | System.err.println("resolutionMs must be positive."); 347 | System.exit(1); 348 | } 349 | } catch (Exception e) { 350 | error = true; 351 | errorMessage = "Error: launched with the following args:\n"; 352 | 353 | for (String arg : args) { 354 | errorMessage += arg + " "; 355 | } 356 | errorMessage += "\nWhich was parsed as an error, indicated by the following exception:\n" + e; 357 | 358 | System.err.println(errorMessage); 359 | 360 | String validArgs = 361 | "\"[-v] [-c] [-x controlProcessArgs] [-o] [-0] [-n] [-p pidOfProcessToAttachTo] [-j jHiccupJarFileName] " + 362 | "[-i reportingIntervalMs] [-h] [-t runTimeMs] [-d startDelayMs] " + 363 | "[-l logFileName] [-r resolutionMs] [-terminateWithStdInput] [-f inputFileName]\"\n"; 364 | 365 | System.err.println("valid arguments = " + validArgs); 366 | 367 | System.err.println( 368 | " [-h] help\n" + 369 | " [-v] verbose\n" + 370 | " [-l logFileName] Log hiccup information into logFileName and logFileName.hgrm\n" + 371 | " (will replace occurrences of %pid and %date with appropriate information)\n" + 372 | " [-o] Output log files in CSV format\n" + 373 | " [-c] Launch a control process in a separate JVM\n" + 374 | " logging hiccup data into logFileName.c and logFileName.c.hgrm\n" + 375 | " [-cfmb controlProcessArgs] Control process filter heap size (in MB): only launch control proc if\n" + 376 | " this process's heap size is larger than the -cfmb parameter\n" + 377 | " [-x controlProcessArgs] Pass additional args to the control process JVM\n" + 378 | " [-p pidOfProcessToAttachTo] Attach to the process with given pid and inject jHiccup as an agent\n" + 379 | " [-j jHiccupJarFileName] File name for the jHiccup.jar file, and required with [-p] option above\n" + 380 | " [-d startDelayMs] Delay the beginning of hiccup measurement by\n" + 381 | " startDelayMs milliseconds [default 0]\n" + 382 | " [-0] Start timestamps at 0 (as opposed to at JVM runtime at start point)\n" + 383 | " [-a] Allocate a throwaway object on every sample [default false]\n" + 384 | " [-i reportingIntervalMs] Set reporting interval [default 5000]\n" + 385 | " [-r resolutionMs] Set sampling resolution in milliseconds [default 1]\n" + 386 | " [-t runTimeMs] Limit measurement time [default 0, for infinite]\n" + 387 | " [-terminateWithStdInput] Take over standard input, and terminate process when\n" + 388 | " standard input is severed (useful for control\n" + 389 | " processes that wish to terminate when their launching\n" + 390 | " parent does).\n" + 391 | " [-f inputFileName] Read timestamp and latency data from input file\n" + 392 | " instead of sampling it directly\n" +"" + 393 | " [-fz] (applies only in conjunction with -f) fill in blank time ranges" + 394 | " with zero values. Useful e.g. when processing GC-log derived input.\n" + 395 | " [-s numberOfSignificantValueDigits]\n"); 396 | } 397 | } 398 | } 399 | 400 | public HiccupMeter(final String[] args, String defaultLogFileName) throws FileNotFoundException { 401 | this.setName("HiccupMeter"); 402 | config = new HiccupMeterConfiguration(args, defaultLogFileName); 403 | log = new PrintStream(new FileOutputStream(config.logFileName), false); 404 | histogramLogWriter = new HistogramLogWriter(log); 405 | this.setDaemon(true); 406 | } 407 | 408 | public static class ExecProcess extends Thread { 409 | final String processName; 410 | final String command; 411 | final boolean verbose; 412 | final PrintStream log; 413 | 414 | public ExecProcess(final String command, final String processName, 415 | final PrintStream log, final boolean verbose) { 416 | this.setDaemon(true); 417 | this.setName(processName + "ExecThread"); 418 | this.command = command; 419 | this.processName = processName; 420 | this.log = log; 421 | this.verbose = verbose; 422 | this.start(); 423 | } 424 | 425 | public void run() { 426 | try { 427 | if (verbose) { 428 | log.println("# HiccupMeter Executing " + processName + " command: " + command); 429 | } 430 | final Process p = Runtime.getRuntime().exec(command); 431 | p.waitFor(); 432 | } catch (Exception e) { 433 | System.err.println("HiccupMeter: " + processName + " terminated."); 434 | } 435 | } 436 | } 437 | 438 | class TerminateWithStdInputReader extends Thread { 439 | TerminateWithStdInputReader() { 440 | this.setDaemon(true); 441 | this.setName("terminateWithStdInputReader"); 442 | this.start(); 443 | } 444 | 445 | @Override 446 | public void run() { 447 | // Ensure exit when stdin is severed. 448 | try { 449 | while (System.in.read() >= 0) { 450 | } 451 | System.exit(1); 452 | } catch (Exception e) { 453 | System.exit(1); 454 | } 455 | 456 | } 457 | } 458 | 459 | public class HiccupRecorder extends Thread { 460 | public volatile boolean doRun; 461 | private final boolean allocateObjects; 462 | public volatile Long lastSleepTimeObj; // public volatile to make sure allocs are not optimized away... 463 | protected final SingleWriterRecorder recorder; 464 | 465 | public HiccupRecorder(final SingleWriterRecorder recorder, final boolean allocateObjects) { 466 | this.setDaemon(true); 467 | this.setName("HiccupRecorder"); 468 | this.recorder = recorder; 469 | this.allocateObjects = allocateObjects; 470 | doRun = true; 471 | } 472 | 473 | public void terminate() { 474 | doRun = false; 475 | } 476 | 477 | public long getCurrentTimeMsecWithDelay(final long nextReportingTime) throws InterruptedException { 478 | final long now = System.currentTimeMillis(); 479 | if (now < nextReportingTime) 480 | Thread.sleep(nextReportingTime - now); 481 | return now; 482 | } 483 | 484 | public void run() { 485 | final long resolutionNsec = (long)(config.resolutionMs * 1000L * 1000L); 486 | try { 487 | long shortestObservedDeltaTimeNsec = Long.MAX_VALUE; 488 | long timeBeforeMeasurement = Long.MAX_VALUE; 489 | while (doRun) { 490 | if (config.resolutionMs != 0) { 491 | TimeUnit.NANOSECONDS.sleep(resolutionNsec); 492 | if (allocateObjects) { 493 | // Allocate an object to make sure potential allocation stalls are measured. 494 | lastSleepTimeObj = new Long(timeBeforeMeasurement); 495 | } 496 | } 497 | final long timeAfterMeasurement = System.nanoTime(); 498 | final long deltaTimeNsec = timeAfterMeasurement - timeBeforeMeasurement; 499 | timeBeforeMeasurement = timeAfterMeasurement; 500 | 501 | if (deltaTimeNsec < 0) { 502 | // On the very first iteration (which will not time the loop in it's entirety) 503 | // the delta will be negative, and we'll skip recording. 504 | continue; 505 | } 506 | 507 | if (deltaTimeNsec < shortestObservedDeltaTimeNsec) { 508 | shortestObservedDeltaTimeNsec = deltaTimeNsec; 509 | } 510 | 511 | long hiccupTimeNsec = deltaTimeNsec - shortestObservedDeltaTimeNsec; 512 | 513 | recorder.recordValueWithExpectedInterval(hiccupTimeNsec, resolutionNsec); 514 | } 515 | } catch (InterruptedException e) { 516 | if (config.verbose) { 517 | log.println("# HiccupRecorder interrupted/terminating..."); 518 | } 519 | } 520 | } 521 | } 522 | 523 | class InputRecorder extends HiccupRecorder { 524 | final Scanner scanner; 525 | long prevTimeMsec = 0; 526 | long inputLineTimeMsec = 0; 527 | long msecThatPrecedesInputLine = -1; 528 | double inputLineHiccupTimeMsec = -1; 529 | boolean reportedAfterTerminate = false; 530 | 531 | 532 | InputRecorder(final SingleWriterRecorder recorder, final String inputFileName) { 533 | super(recorder, false); 534 | Scanner newScanner = null; 535 | try { 536 | newScanner = new Scanner(new File(inputFileName)); 537 | } catch (FileNotFoundException e) { 538 | System.err.println("HiccupMeter: Failed to open input file \"" + inputFileName + "\""); 539 | System.exit(-1); 540 | } finally { 541 | scanner = newScanner; 542 | } 543 | } 544 | 545 | long processInputLine(final Scanner scanner, final SingleWriterRecorder recorder) { 546 | if (scanner.hasNextLine()) { 547 | try { 548 | inputLineTimeMsec = (long) scanner.nextDouble(); // Timestamp is expect to be in millis 549 | inputLineHiccupTimeMsec = scanner.nextDouble(); // Latency is expected to be in millis 550 | msecThatPrecedesInputLine = config.fillInZerosInInputFile ? 551 | inputLineTimeMsec - (long)Math.ceil(inputLineHiccupTimeMsec) : 552 | inputLineTimeMsec; 553 | if (inputLineTimeMsec < prevTimeMsec) { 554 | return -1; // Input time is going backwards. Can't have that. Terminate. 555 | } 556 | return inputLineTimeMsec; 557 | } catch (java.util.NoSuchElementException e) { 558 | return -1; 559 | } 560 | } 561 | return -1; 562 | } 563 | 564 | @Override 565 | public long getCurrentTimeMsecWithDelay(final long nextReportingTime) throws InterruptedException { 566 | // The following loop will terminate either at the next reporting time, or when input is exhausted: 567 | do { 568 | if (nextReportingTime < msecThatPrecedesInputLine) { 569 | // Nothing in the input before the nextReportingTime: 570 | 571 | long numberOfTicksBeforeNextReportingTime = 572 | (long) ((nextReportingTime - prevTimeMsec) / config.resolutionMs); 573 | if (config.fillInZerosInInputFile && (numberOfTicksBeforeNextReportingTime > 0)) { 574 | // fill in blank time between prevTimeMsec and nextReportingTime with zero values: 575 | recorder.recordValueWithCount(0L, numberOfTicksBeforeNextReportingTime); 576 | } 577 | 578 | // Indicate that we've processed input up to nextReportingTime: 579 | prevTimeMsec = nextReportingTime; 580 | 581 | return nextReportingTime; 582 | } else if (msecThatPrecedesInputLine >= prevTimeMsec) { 583 | // Process previously read input: 584 | long numberOfTicksBeforeInputHiccup = 585 | (long) ((msecThatPrecedesInputLine - prevTimeMsec) / config.resolutionMs); 586 | if (config.fillInZerosInInputFile && (numberOfTicksBeforeInputHiccup > 0)) { 587 | // Fill in blank time between previously processed time and the hiccup with zero values: 588 | recorder.recordValueWithCount(0L, numberOfTicksBeforeInputHiccup); 589 | } 590 | 591 | final long hiccupTimeNsec = (long) (inputLineHiccupTimeMsec * 1000000.0); 592 | recorder.recordValueWithExpectedInterval(hiccupTimeNsec, (long) (config.resolutionMs * 1000000L)); 593 | 594 | // indicate that we've processed input up to the end of the previously read line: 595 | prevTimeMsec = inputLineTimeMsec; 596 | } 597 | // Read next line: 598 | } while (processInputLine(scanner, recorder) >= 0); 599 | 600 | if (!reportedAfterTerminate) { 601 | // Fill last report with zeros if/as needed: 602 | long numberOfTicksBeforeNextReportingTime = 603 | (long) ((nextReportingTime - prevTimeMsec) / config.resolutionMs); 604 | if (config.fillInZerosInInputFile && (numberOfTicksBeforeNextReportingTime > 0)) { 605 | // fill in blank time between prevTimeMsec and nextReportingTime with zero values: 606 | recorder.recordValueWithCount(0L, numberOfTicksBeforeNextReportingTime); 607 | } 608 | 609 | reportedAfterTerminate = true; 610 | return nextReportingTime; 611 | } 612 | // Input exhausted : 613 | return -1; 614 | } 615 | 616 | @Override 617 | public void run() { 618 | try { 619 | while (doRun) { 620 | Thread.sleep(10); 621 | } 622 | } catch (InterruptedException e) { 623 | if (config.verbose) { 624 | log.println("# HiccupRecorder interrupted/terminating..."); 625 | } 626 | } 627 | } 628 | } 629 | 630 | public HiccupRecorder createHiccupRecorder(SingleWriterRecorder recorder) { 631 | return new HiccupRecorder(recorder, config.allocateObjects); 632 | } 633 | 634 | public String getVersionString() { 635 | return versionString; 636 | } 637 | 638 | @Override 639 | public void run() { 640 | final SingleWriterRecorder recorder = 641 | new SingleWriterRecorder( 642 | config.lowestTrackableValue, 643 | config.highestTrackableValue, 644 | config.numberOfSignificantValueDigits 645 | ); 646 | 647 | Histogram intervalHistogram = null; 648 | 649 | HiccupRecorder hiccupRecorder; 650 | 651 | final long uptimeAtInitialStartTime = ManagementFactory.getRuntimeMXBean().getUptime(); 652 | long now = System.currentTimeMillis(); 653 | long jvmStartTime = now - uptimeAtInitialStartTime; 654 | long reportingStartTime = jvmStartTime; 655 | 656 | if (config.inputFileName == null) { 657 | // Normal operating mode. 658 | // Launch a hiccup recorder, a process termination monitor, and an optional control process: 659 | hiccupRecorder = this.createHiccupRecorder(recorder); 660 | if (config.terminateWithStdInput) { 661 | new TerminateWithStdInputReader(); 662 | } 663 | if (config.controlProcessCommand != null) { 664 | new ExecProcess(config.controlProcessCommand, "ControlProcess", log, config.verbose); 665 | } 666 | } else { 667 | // Take input from file instead of sampling it ourselves. 668 | // Launch an input hiccup recorder, but no termination monitoring or control process: 669 | hiccupRecorder = new InputRecorder(recorder, config.inputFileName); 670 | } 671 | 672 | histogramLogWriter.outputComment("[Logged with " + getVersionString() + "]"); 673 | histogramLogWriter.outputLogFormatVersion(); 674 | 675 | try { 676 | final long startTime; 677 | 678 | if (config.inputFileName == null) { 679 | // Normal operating mode: 680 | if (config.startDelayMs > 0) { 681 | // Run hiccup recorder during startDelayMs time to let code warm up: 682 | hiccupRecorder.start(); 683 | while (config.startDelayMs > System.currentTimeMillis() - jvmStartTime) { 684 | Thread.sleep(100); 685 | } 686 | hiccupRecorder.terminate(); 687 | hiccupRecorder.join(); 688 | 689 | recorder.reset(); 690 | hiccupRecorder = new HiccupRecorder(recorder, config.allocateObjects); 691 | } 692 | hiccupRecorder.start(); 693 | startTime = System.currentTimeMillis(); 694 | if (config.startTimeAtZero) { 695 | reportingStartTime = startTime; 696 | } 697 | 698 | histogramLogWriter.outputStartTime(reportingStartTime); 699 | histogramLogWriter.setBaseTime(reportingStartTime); 700 | 701 | } else { 702 | // Reading from input file, not sampling ourselves...: 703 | hiccupRecorder.start(); 704 | now = reportingStartTime = hiccupRecorder.getCurrentTimeMsecWithDelay(0); 705 | 706 | while (config.startDelayMs > now - reportingStartTime) { 707 | now = hiccupRecorder.getCurrentTimeMsecWithDelay(0); 708 | } 709 | 710 | startTime = now; 711 | 712 | histogramLogWriter.outputComment("[Data read from input file \"" + config.inputFileName + "\" at " + new Date() + "]"); 713 | } 714 | 715 | histogramLogWriter.outputLegend(); 716 | 717 | long nextReportingTime = startTime + config.reportingIntervalMs; 718 | long intervalStartTimeMsec = 0; 719 | 720 | while ((now >= 0) && ((config.runTimeMs == 0) || (config.runTimeMs >= now - startTime))) { 721 | now = hiccupRecorder.getCurrentTimeMsecWithDelay(nextReportingTime); // could return -1 to indicate termination 722 | if (now >= nextReportingTime) { 723 | // Get the latest interval histogram and give the recorder a fresh Histogram for the next interval 724 | intervalHistogram = recorder.getIntervalHistogram(intervalHistogram); 725 | 726 | while (now >= nextReportingTime) { 727 | nextReportingTime += config.reportingIntervalMs; 728 | } 729 | 730 | if (config.inputFileName != null) { 731 | // When read from input file, use timestamps from file input for start/end of log intervals: 732 | intervalHistogram.setStartTimeStamp(intervalStartTimeMsec); 733 | intervalHistogram.setEndTimeStamp(now); 734 | intervalStartTimeMsec = now; 735 | } 736 | 737 | if (intervalHistogram.getTotalCount() > 0) { 738 | histogramLogWriter.outputIntervalHistogram(intervalHistogram); 739 | } 740 | } 741 | } 742 | } catch (InterruptedException e) { 743 | if (config.verbose) { 744 | log.println("# HiccupMeter terminating..."); 745 | } 746 | } 747 | 748 | try { 749 | hiccupRecorder.terminate(); 750 | hiccupRecorder.join(); 751 | } catch (InterruptedException e) { 752 | if (config.verbose) { 753 | log.println("# HiccupMeter terminate/join interrupted"); 754 | } 755 | } 756 | } 757 | 758 | public static HiccupMeter commonMain(final String[] args, boolean exitOnError) { 759 | HiccupMeter hiccupMeter = null; 760 | try { 761 | hiccupMeter = new HiccupMeter(args, defaultHiccupLogFileName); 762 | 763 | if (hiccupMeter.config.attachToProcess) { 764 | String errorMessage = "Cannot use -p option with HiccupMeter (use HiccupMeterAttacher instead)"; 765 | if (exitOnError) { 766 | System.err.println(errorMessage); 767 | System.exit(1); 768 | } else { 769 | throw new RuntimeException("Error: " + errorMessage); 770 | } 771 | } 772 | 773 | if (hiccupMeter.config.error) { 774 | if (exitOnError) { 775 | System.exit(1); 776 | } else { 777 | throw new RuntimeException("Error: " + hiccupMeter.config.errorMessage); 778 | } 779 | } 780 | 781 | if (hiccupMeter.config.verbose) { 782 | hiccupMeter.log.print("# Executing: HiccupMeter"); 783 | for (String arg : args) { 784 | hiccupMeter.log.print(" " + arg); 785 | } 786 | hiccupMeter.log.println(""); 787 | } 788 | 789 | hiccupMeter.start(); 790 | 791 | } catch (FileNotFoundException e) { 792 | System.err.println("HiccupMeter: Failed to open log file."); 793 | } 794 | return hiccupMeter; 795 | } 796 | 797 | public static void agentmain(String argsString, java.lang.instrument.Instrumentation inst) { 798 | final String[] args = ((argsString != null) && !argsString.equals("")) ? argsString.split("[ ,;]+") : new String[0]; 799 | final String avoidRecursion = System.getProperty("org.jhiccup.avoidRecursion"); 800 | if (avoidRecursion != null) { 801 | return; // If this is a -c invocation, we do not want the agent to do anything... 802 | } 803 | commonMain(args, false); 804 | } 805 | 806 | public static void premain(String argsString, java.lang.instrument.Instrumentation inst) { 807 | final String[] args = ((argsString != null) && !argsString.equals("")) ? argsString.split("[ ,;]+") : new String[0]; 808 | final String avoidRecursion = System.getProperty("org.jhiccup.avoidRecursion"); 809 | if (avoidRecursion != null) { 810 | return; // If this is a -c invocation, we do not want the agent to do anything... 811 | } 812 | commonMain(args, true); 813 | } 814 | 815 | public static void main(final String[] args) { 816 | final HiccupMeter hiccupMeter = commonMain(args, true); 817 | 818 | if (hiccupMeter != null) { 819 | // The HiccupMeter thread, on it's own, will not keep the JVM from exiting. If nothing else 820 | // is running (i.e. we we are the main class), then keep main thread from exiting 821 | // until the HiccupMeter thread does... 822 | try { 823 | hiccupMeter.join(); 824 | } catch (InterruptedException e) { 825 | if (hiccupMeter.config.verbose) { 826 | hiccupMeter.log.println("# HiccupMeter main() interrupted"); 827 | } 828 | } 829 | } 830 | } 831 | } 832 | 833 | -------------------------------------------------------------------------------- /src/main/java/org/jhiccup/HiccupMeterAttacher.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Written by Gil Tene of Azul Systems, and released to the public domain, 3 | * as explained at http://creativecommons.org/publicdomain/zero/1.0/ 4 | * 5 | * @author Gil Tene 6 | */ 7 | 8 | package org.jhiccup; 9 | 10 | import com.sun.tools.attach.AgentInitializationException; 11 | import com.sun.tools.attach.AgentLoadException; 12 | import com.sun.tools.attach.AttachNotSupportedException; 13 | import com.sun.tools.attach.VirtualMachine; 14 | 15 | import java.io.*; 16 | 17 | /** 18 | * Attach to another process and launch a jHiccup agent in it. 19 | * 20 | * Uses HiccupMeter's HiccupMeterConfiguration class to parse and prepare arguments. 21 | * 22 | */ 23 | 24 | 25 | public class HiccupMeterAttacher { 26 | 27 | public static void main(final String[] args) { 28 | HiccupMeter.HiccupMeterConfiguration config = 29 | new HiccupMeter.HiccupMeterConfiguration(args, HiccupMeter.defaultHiccupLogFileName); 30 | 31 | if (config.error) { 32 | System.exit(1); 33 | } 34 | 35 | if (!config.attachToProcess) { 36 | System.err.println("HiccupMeterAttacher: must be used with -p option."); 37 | System.exit(1); 38 | } 39 | 40 | try { 41 | // We are supposed to attach to another process and launch a jHiccup agent there, not here. 42 | if (config.verbose) { 43 | System.out.println("Attaching to process " + config.pidOfProcessToAttachTo + 44 | " and launching jHiccup agent from jar " + config.agentJarFileName + 45 | " with args: " + config.agentArgs ); 46 | } 47 | VirtualMachine vm = VirtualMachine.attach(config.pidOfProcessToAttachTo); 48 | vm.loadAgent(config.agentJarFileName, config.agentArgs); 49 | vm.detach(); 50 | System.exit(0); 51 | } catch (IOException ex) { 52 | ex.printStackTrace(); 53 | System.exit(1); 54 | } catch (AttachNotSupportedException ex) { 55 | ex.printStackTrace(); 56 | System.exit(1); 57 | } catch (AgentInitializationException ex) { 58 | ex.printStackTrace(); 59 | System.exit(1); 60 | } catch ( AgentLoadException ex) { 61 | ex.printStackTrace(); 62 | System.exit(1); 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/main/java/org/jhiccup/Idle.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Written by Gil Tene of Azul Systems, and released to the public domain, 3 | * as explained at http://creativecommons.org/publicdomain/zero/1.0/ 4 | * 5 | * @author Gil Tene 6 | */ 7 | 8 | package org.jhiccup; 9 | 10 | import java.io.*; 11 | 12 | /** 13 | * Idle: A simple java program that idles for a configurable amount of time 14 | * and then exits. It will also exit if it's stdin pipe is severed. Useful 15 | * for testing and demonstrating wrappers (such as HiccupMeter/jHiccup), 16 | * as well as for running control process tests concurrently with observed 17 | * applications. 18 | */ 19 | 20 | public class Idle extends Thread { 21 | 22 | class IdleConfiguration { 23 | public long runTimeMs = 10000; 24 | public boolean verbose = false; 25 | public boolean useIdleReader = true; 26 | 27 | public void parseArgs(String[] args) { 28 | try { 29 | for (int i = 0; i < args.length; ++i) { 30 | if (args[i].equals("-v")) { 31 | config.verbose = true; 32 | } else if (args[i].equals("-n")) { 33 | config.useIdleReader = false; 34 | } else if (args[i].equals("-t")) { 35 | runTimeMs = Long.parseLong(args[++i]); // lgtm [java/index-out-of-bounds] 36 | } else { 37 | throw new Exception("Invalid args"); 38 | } 39 | } 40 | } catch (Exception e) { 41 | System.err.println("Usage: java Idle [-v] [-n] [-t runTimeMs]"); 42 | System.exit(1); 43 | } 44 | } 45 | } 46 | 47 | IdleConfiguration config = new IdleConfiguration(); 48 | 49 | class IdleReader extends Thread { 50 | IdleReader() { 51 | this.setDaemon(true); 52 | this.setName("IdleReader"); 53 | this.start(); 54 | } 55 | 56 | public void run() { 57 | // Ensure Idle exit when stdin is severed. 58 | try { 59 | while (System.in.read() >= 0) { 60 | } 61 | System.exit(1); 62 | } catch (Exception e) { 63 | System.exit(1); 64 | } 65 | 66 | } 67 | } 68 | 69 | public Idle() throws FileNotFoundException { 70 | } 71 | 72 | public Idle(String[] args) throws FileNotFoundException { 73 | config.parseArgs(args); 74 | } 75 | 76 | public void terminate() { 77 | this.interrupt(); 78 | } 79 | 80 | public void run() { 81 | if (config.useIdleReader) { 82 | new IdleReader(); 83 | } 84 | try { 85 | if (config.verbose) 86 | System.out.println("Idling for " + config.runTimeMs + "msec..."); 87 | 88 | long startTime = System.currentTimeMillis(); 89 | while ((config.runTimeMs == 0) || (config.runTimeMs > System.currentTimeMillis() - startTime)) { 90 | Thread.sleep(100); // Just wait to be interrupted/terminated or for time to expire... 91 | } 92 | } catch (InterruptedException e) { 93 | if (config.verbose) System.out.println("Idle terminating..."); 94 | } 95 | } 96 | 97 | public static void main(String[] args) { 98 | try { 99 | Idle idler = new Idle(args); 100 | 101 | 102 | if (idler.config.verbose) { 103 | System.out.print("Executing: idler"); 104 | 105 | for (String arg : args) { 106 | System.out.print(" " + arg); 107 | } 108 | System.out.println(""); 109 | } 110 | 111 | idler.start(); 112 | 113 | 114 | try { 115 | idler.join(); 116 | } catch (InterruptedException e) { 117 | if (idler.config.verbose) System.out.println("idler main() interrupted"); 118 | } 119 | // (if you wanted idler to terminate early, call idler.terminate() )... 120 | } catch (FileNotFoundException e) { 121 | System.err.println("Failed to open log file."); 122 | } 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/main/java/org/jhiccup/Version.java.template: -------------------------------------------------------------------------------- 1 | /** 2 | * Written by Gil Tene of Azul Systems, and released to the public domain, 3 | * as explained at http://creativecommons.org/publicdomain/zero/1.0/ 4 | * 5 | * @author Gil Tene 6 | */ 7 | 8 | package org.jhiccup; 9 | 10 | public final class Version { 11 | public static final String version="$VERSION$"; 12 | public static final String build_time="$BUILD_TIME$"; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/org/jhiccup/HiccupConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package org.jhiccup; 7 | 8 | import org.junit.Test; 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * 13 | * @author sgrinev 14 | */ 15 | public class HiccupConfigurationTest { 16 | 17 | @Test 18 | public void testHelp() { 19 | 20 | HiccupMeter.HiccupMeterConfiguration config = 21 | new HiccupMeter.HiccupMeterConfiguration(new String[] {"-h"}, null); 22 | assertTrue(config.error); // NB: a bug 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/org/jhiccup/HiccupMeterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package org.jhiccup; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | /** 13 | * 14 | * @author sgrinev 15 | */ 16 | public class HiccupMeterTest { 17 | 18 | @Before 19 | public void setUp() { 20 | System.out.println("Vendor = " + System.getProperty("java.vendor")); 21 | System.out.println("Version = " + System.getProperty("java.version")); 22 | } 23 | 24 | @Test 25 | public void testAttach() { 26 | String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 27 | System.out.println("My pid is " + pid); 28 | //HiccupMeterAttacher.main(new String[]{"-p", pid, "-j", "/Users/sgrinev/ws/jHiccup/jHiccup.jar"}); 29 | } 30 | } 31 | --------------------------------------------------------------------------------