├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── config
└── checkstyle
│ └── checkstyle.xml
├── deps
└── jHiccup-2.0.5
│ ├── COPYING.txt
│ ├── LICENSE.txt
│ ├── README.md
│ ├── jHiccup
│ ├── jHiccup.jar
│ ├── jHiccupLogProcessor
│ ├── jHiccupPlotter.xls
│ ├── pom.xml
│ └── src
│ └── main
│ ├── assembly
│ └── dist.xml
│ └── java
│ └── org
│ └── jhiccup
│ ├── HiccupMeter.java
│ ├── HiccupMeterAttacher.java
│ ├── Idle.java
│ ├── Version.java
│ └── Version.java.template
├── doc
├── application.png
├── baseline-chart.png
├── baseline_vs_no_workqueue.png
├── isolcpus-chart-log-scale.png
├── isolcpus-chart.png
├── lstopo-cpus.png
├── no_watchdog.png
├── performance-chart.png
├── pinned-thread-chart.png
└── reduced_interrupts.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── settings.gradle
└── src
└── main
├── java
├── com
│ └── epickrram
│ │ └── workshop
│ │ └── perf
│ │ ├── UberMain.java
│ │ ├── app
│ │ ├── AppMain.java
│ │ ├── jitter
│ │ │ └── Spinners.java
│ │ ├── message
│ │ │ ├── JournalEntry.java
│ │ │ └── Packet.java
│ │ └── processors
│ │ │ ├── Accumulator.java
│ │ │ ├── EventHandlerAdapter.java
│ │ │ ├── InputReader.java
│ │ │ ├── Journaller.java
│ │ │ └── ThreadAffinityEventHandler.java
│ │ ├── config
│ │ ├── CommandLineArgs.java
│ │ └── Overrides.java
│ │ ├── reporting
│ │ ├── AccumulatorReporter.java
│ │ ├── HistogramReporter.java
│ │ ├── JournalReporter.java
│ │ ├── PostProcess.java
│ │ └── ReportFormat.java
│ │ ├── setup
│ │ └── InputGenerator.java
│ │ └── support
│ │ ├── DaemonThreadFactory.java
│ │ ├── EncodedHistogramReporter.java
│ │ ├── Histograms.java
│ │ ├── NanoTimer.java
│ │ ├── SpinLoopHintBusySpinWaitStrategy.java
│ │ ├── SystemNanoTimer.java
│ │ └── Threads.java
└── org
│ └── performancehints
│ └── SpinHint.java
├── python
├── perf_sched_stat_runtime.py
├── perf_softirq_duration.py
└── perf_workqueue_duration.py
└── shell
├── chart_accumulator_message_transit_latency.sh
├── convert_encoded_histogram.sh
├── get_pids.sh
├── run_test.sh
├── set_cpu_governor.sh
├── set_irq_affinity.sh
├── set_rcu_thread_affinity.sh
├── set_thread_affinity.py
├── set_watchdog_thread_affinity.sh
├── terminal_latency.cmd
└── xlabels.dat
/.gitignore:
--------------------------------------------------------------------------------
1 | perf-workshop.iws
2 | perf-workshop.ipr
3 | perf-workshop.iml
4 |
5 | out/
6 | build/
7 | .gradle/
8 | .idea/
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | System Jitter Utility
2 | =====================
3 |
4 | A test program for exploring causes of jitter.
5 |
6 |
7 | The application
8 | ===============
9 |
10 | 
11 |
12 | The application consists of 3 threads:
13 |
14 | 1. The producer thread - responsible for reading data from a memory-mapped file, inserting a timestamp, and publishing messages onto a queue (an instance of the [Disruptor] (https://github.com/LMAX-Exchange/disruptor)).
15 | 2. The accumulator (logic in the above diagram) thread - records a timestamp when it pulls a message from the queue, and stores the queue transit latency in a histogram.
16 | 3. The journaller thread - records a timestamp when it pulls a message from the queue, writes an entry to a journal containing the queue transit latency.
17 |
18 | All timestamps are generated by calling `System.nanoTime()`.
19 |
20 | The producer thread will busy-spin for ten microseconds between each publication. Consumer threads are busy-waiting on the head of the queue for messages to arrive from the producer.
21 |
22 | The application is garbage-free, and [guaranteed safepoints] (https://epickrram.blogspot.co.uk/2015/08/jvm-guaranteed-safepoints.html) are disabled, so there should be no jitter introduced by the JVM itself.
23 |
24 | Four latencies are recorded:
25 |
26 | 1. Queue transit time for accumulator thread
27 | 2. Queue transit time for journaller thread
28 | 3. Inter-message time for accumulator thread
29 | 4. Inter-message time for journaller thread
30 |
31 | On system exit, full histograms of these values are generated for post-processing (placed in `/tmp/` by default).
32 |
33 |
34 | Requirements
35 | ============
36 |
37 | 1. JDK 8+
38 |
39 |
40 | Tools to install
41 | ================
42 |
43 | Install the following tools in order to work through the exercises:
44 |
45 | 1. gnuplot
46 | 2. perf
47 | 3. hwloc
48 | 4. trace-cmd
49 | 5. powertop
50 |
51 |
52 | Using
53 | =====
54 |
55 | 1. Clone this git repository
56 | 2. Build the library: `./gradlew bundleJar`
57 | 3. Run it: `cd src/main/shell && bash ./run_test.sh BASELINE`
58 |
59 |
60 | Output
61 | ======
62 |
63 | The `run_test.sh` script will run the application, using 'BASELINE' as a label. At exit, the application will print out a number of latency histograms.
64 |
65 | Below is an excerpt of the output containing the histogram of latencies recorded between the producer thread and the accumulator thread.
66 |
67 |
68 | == Accumulator Message Transit Latency (ns) ==
69 | mean 60879
70 | min 76
71 | 50.00% 168
72 | 90.00% 256
73 | 99.00% 2228239
74 | 99.90% 8126495
75 | 99.99% 10485823
76 | 99.999% 11534399
77 | 99.9999% 11534399
78 | max 11534399
79 | count 3595101
80 |
81 |
82 | So for this run, 3.5m messages were passed through the queue, the mean latency was around 60 microseconds,
83 | min latency was 75 nanoseconds, and the max latency was over 11 milliseconds.
84 |
85 | These numbers can be plotted on a chart using the following command, executed from `src/main/shell`:
86 |
87 | `bash ./chart_accumulator_message_transit_latency.sh`
88 |
89 | and viewed with the following command:
90 |
91 | `gnuplot ./accumulator_message_transit_latency.cmd`
92 |
93 | producing something that looks like this chart:
94 |
95 | 
96 |
97 |
98 | Why so slow?
99 | ============
100 |
101 | From these first results, we can see that at the 99th percentile, inter-thread latency was over 2 milliseconds,
102 | meaning that 1 in 100 messages took 2ms or longer to transit between two threads.
103 |
104 | Since no other work is being done by this program, the workload is constant, and there are no runtime pauses,
105 | where is this jitter coming from?
106 |
107 | Below are a series of steps working through some causes of system jitter on a modern Linux kernel
108 | (my laptop is running Fedora 22 on kernel 4.0.4). Most of these techniques have been tested on a 3.18 kernel,
109 | older versions may not have the same features/capabilities.
110 |
111 |
112 | CPU speed
113 | =========
114 |
115 | Modern CPUs (especially on laptops) are designed to be power efficient, this means that the OS will typically try
116 | to scale down the clock rate when there is no activity. On Intel CPUs, this is partially handled using power-states,
117 | which allow the OS to reduce CPU frequency, meaning less power draw, and less thermal overhead.
118 |
119 | On current kernels, this is handled by the CPU scaling governor. You can check your current setting by looking in the file
120 |
121 | `/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor`
122 |
123 | on my laptop, this is set to `powersave` mode. To see available governors:
124 |
125 | `cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors`
126 |
127 | which tells me that I have two choices:
128 |
129 | 1. performance
130 | 2. powersave
131 |
132 | Before making a change though, let's make sure that powersave is actually causing us issues.
133 |
134 | To do this, we can use [perf_events] (https://perf.wiki.kernel.org/index.php/Main_Page)
135 | to monitor the CPU's P-state while the application is running:
136 |
137 | `perf record -e "power:cpu_frequency" -a`
138 |
139 | This command will sample the cpu_frequency trace point written to by the intel cpufreq driver on all CPUs. This information comes
140 | from an MSR on the chip which holds the FSB speed.
141 |
142 | Hit Ctrl+c once the application has finished running, then use `perf script` to view the available output.
143 |
144 | Filtering entries to include only those samples taken when `java` was executing shows some variation in the reported frequency:
145 |
146 | java 2804 [003] 3327.796741: power:cpu_frequency: state=1500000 cpu_id=3
147 | java 2804 [003] 3328.089969: power:cpu_frequency: state=3000000 cpu_id=3
148 | java 2804 [003] 3328.139009: power:cpu_frequency: state=2500000 cpu_id=3
149 | java 2804 [003] 3328.204063: power:cpu_frequency: state=1000000 cpu_id=3
150 |
151 |
152 | Set the scaling governor to performance mode to reduce this:
153 |
154 | `sudo bash ./set_cpu_governor.sh performance`
155 |
156 | and re-run the test while using `perf` to record `cpu_frequency` events. If the change has taken effect,
157 | there should be no events output by `perf script`.
158 |
159 |
160 | Running the test again with the performance governor enabled produces better results for inter-thread latency:
161 |
162 | == Accumulator Message Transit Latency (ns) ==
163 | mean 23882
164 | min 84
165 | 50.00% 152
166 | 90.00% 208
167 | 99.00% 589827
168 | 99.90% 4456479
169 | 99.99% 7340063
170 | 99.999% 7864351
171 | 99.9999% 8126495
172 | max 8126495
173 | count 3595101
174 |
175 |
176 | Though there is still a max latency of 8ms, it has been reduced from the previous value of 11ms.
177 |
178 | The effect is clearly visible when added to the chart. To add the new data, go through the steps followed earlier:
179 |
180 | `bash ./chart_accumulator_message_transit_latency.sh`
181 | `gnuplot ./accumulator_message_transit_latency.cmd`
182 |
183 | 
184 |
185 |
186 | Process migration
187 | =================
188 |
189 | Another possible cause of scheduling jitter is likely to be down to the OS scheduler moving processes around as different tasks
190 | become runnable. The important threads in the application are at the mercy of the scheduler, which can, at any time
191 | decide to run another process on the current CPU. When this happens, the running thread's context will be saved, and it will
192 | be shifted back into the schedulers run-queue (or possibly migrated to another CPU entirely).
193 |
194 | To find out whether this is happening to the threads in our application, we can turn to `perf` again and sample trace events
195 | emitted by the scheduler. First, record the PIDs of the two important threads from the application (producer and accumulator):
196 |
197 | Starting replay at Thu Sep 24 14:17:31 BST 2015
198 | Accumulator thread has pid: 11372
199 | Journaller thread has pid: 11371
200 | Producer thread has pid: 11370
201 | Warm-up complete at Thu Sep 24 14:17:35 BST 2015
202 | Pausing for 10 seconds...
203 |
204 | Once warm-up has completed, record the scheduler events for the specific PIDs of interest:
205 |
206 | `perf record -e "sched:sched_stat_runtime" -t 11370 -t 11372`
207 |
208 | This command will record events emitted by the scheduler to update a task's runtime statistics. The recording session will exit once those processes complete. Running `perf script` again will show the captured events:
209 |
210 | `java 11372 [001] 3055.140623: sched:sched_stat_runtime: comm=java pid=11372 runtime=1000825 [ns] vruntime=81510486145 [ns]`
211 |
212 | The line above shows, among other things, what CPU the process was executing on when stats were updated. In this case, the process was running on CPU 001. A bit of sorting and counting will show exactly how the process was moved around the available CPUs during its lifetime:
213 |
214 | `perf script | grep "java 11372" | awk '{print $3}' | sort | uniq -c`
215 |
216 | 16071 [000]
217 | 10858 [001]
218 | 5778 [002]
219 | 7230 [003]
220 |
221 |
222 | So this thread mostly ran on CPUs 0 and 1, but also spent some time on CPUs 2 and 3. Moving the process around is going to require a context switch, and cache invalidation effects. While these are unlikely to be the sources of maximum latency, in order to start improving the worst-case, it will be necessary to stop migration of these processes.
223 |
224 | The application allows the user to select a target CPU for any of the three processing threads via a config file `/tmp/perf-workshop.properties`. Edit the file, and select two different CPUs for the producer and accumulator threads:
225 |
226 | perf.workshop.affinity.producer=1
227 | perf.workshop.affinity.accumulator=3
228 |
229 |
230 | Re-running the test shows a large improvement:
231 |
232 |
233 | 
234 |
235 |
236 | This result implies that forcing the threads to run on a single CPU can help reduce inter-thread latency. Whether this is down to the scheduler making better decisions about where to run other processes, or simply because there is less context switching is not clear.
237 |
238 | One thing to look out for is the fact that we have not stopped the scheduler from running other tasks on those CPUs. We are still seeing multi-millisecond delays in message passing, and this could be down to other processes being run on the CPU that the application thread has been restricted to.
239 |
240 | Returning to `perf` and this time capturing all `sched_stat_runtime` events for a specific CPU (in this case 1) will show what other processes are being scheduled while the application is running:
241 |
242 | `perf record -e "sched:sched_stat_runtime" -C 1`
243 |
244 | Stripping out everything but the process name, and counting occurrences in the event trace shows that while the java application was running most of the time, there are plenty of other processes that were scheduled during the application's execution time:
245 |
246 | 45514 java
247 | 60 kworker/1:2
248 | 26 irq/39-DLL0665:
249 | 24 rngd
250 | 15 rcu_sched
251 | 9 gmain
252 | 8 goa-daemon
253 | 7 chrome
254 | 6 ksoftirqd/1
255 | 5 rtkit-daemon
256 |
257 |
258 | CPU Isolation
259 | =============
260 |
261 | At this point, it's time to remove the target CPUs from the OS's scheduling domain. This can be done with the `isolcpus` boot parameter (i.e. add `isolcpus=1,3` to `grub.conf`), or by using the `cset` command from the `cpuset` package.
262 |
263 | In this case, I'm using `isolcpus` to stop the scheduler from running other userland processes on CPUs 1 & 3. The difference in inter-thread latency is dramatic:
264 |
265 |
266 | == Accumulator Message Transit Latency (ns) ==
267 | mean 144
268 | min 84
269 | 50.00% 144
270 | 90.00% 160
271 | 99.00% 208
272 | 99.90% 512
273 | 99.99% 2432
274 | 99.999% 3584
275 | 99.9999% 11776
276 | max 14848
277 | count 3595101
278 |
279 |
280 | The difference is so great, that it's necessary to use a log-scale for the y-axis of the chart.
281 |
282 |
283 | 
284 |
285 |
286 | Note that the difference will not be so great on a server-class machine with lots of spare processing power. The effect here is magnified by the fact that the OS only has 4 CPUs (on my laptop) to work with, and a desktop distribution of Linux. So there is much more scheduling pressure than would be present on a server-class machine.
287 |
288 | Using `perf` once again to confirm that other processes are not running on the reserved CPUs shows that there is still some contention to deal with:
289 |
290 | 81130 java
291 | 2 ksoftirqd/1
292 | 43 kworker/1:0
293 | 1 kworker/1:1H
294 | 2 kworker/3:1
295 | 1 kworker/3:1H
296 | 11 swapper
297 |
298 | These processes starting with 'k' are kernel threads that deal with house-keeping tasks on behalf of the OS, 'swapper' is the Linux idle process, which is scheduled whenever there is no work to be executed on a CPU.
299 |
300 |
301 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 LMAX Ltd
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | apply plugin: 'java'
17 | apply plugin: 'idea'
18 | apply plugin: 'eclipse'
19 | apply plugin: 'checkstyle'
20 |
21 | defaultTasks 'checkstyleMain', 'checkstyleTest', 'build'
22 |
23 | version = new Version(major: 0, revision: 1)
24 |
25 | ext {
26 | fullName = 'Perf Workshop'
27 | fullDescription = 'Perf Workshop example code'
28 | teamName = ''
29 | siteUrl = 'http://lmax.com'
30 | sourceUrl = ''
31 |
32 | javaCompilerExecutable = System.env['JAVA_HOME'] ? System.env['JAVA_HOME'] + '/bin/javac' : 'javac'
33 | }
34 |
35 | sourceSets {
36 | main {
37 | java {
38 | srcDir 'src/main/java'
39 | }
40 | }
41 |
42 | test {
43 | java {
44 | srcDir 'src/test/java'
45 | }
46 | resources {
47 | srcDir 'src/test/resources'
48 | }
49 | }
50 | }
51 |
52 | repositories {
53 | mavenCentral()
54 | }
55 |
56 | dependencies {
57 | compile 'com.lmax:disruptor:3.3.2', 'org.hdrhistogram:HdrHistogram:2.1.4', 'com.google.code.gson:gson:2.3.1', 'com.beust:jcommander:1.48', 'net.openhft:affinity:3.1.7'
58 | }
59 |
60 | sourceCompatibility = 1.8
61 | targetCompatibility = 1.8
62 |
63 | jar {
64 | baseName = project.name
65 |
66 | manifest.attributes('Built-By': System.properties['user.name'],
67 | 'Bundle-Name': fullName,
68 | 'Bundle-Vendor': teamName,
69 | 'Bundle-Description': fullDescription,
70 | 'Bundle-DocURL': siteUrl)
71 | }
72 |
73 | task sourcesJar(type: Jar) {
74 | classifier = 'sources'
75 | from sourceSets.main.allSource
76 | }
77 |
78 | task javadocJar(type: Jar, dependsOn: javadoc) {
79 | classifier = 'javadoc'
80 | from javadoc.destinationDir
81 | }
82 |
83 | task bundleJar(type: Jar) {
84 | manifest {
85 | attributes 'Implementation-Title': 'Perf Workshop Bundle',
86 | 'Implementation-Version': version,
87 | 'Main-Class': 'com.epickrram.workshop.perf.UberMain'
88 | }
89 | baseName = project.name + '-all'
90 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
91 | with jar
92 | }
93 |
94 | artifacts {
95 | archives sourcesJar, javadocJar
96 | }
97 |
98 | checkstyle {
99 | toolVersion = "6.3"
100 | }
101 |
102 | task wrapper(type: Wrapper) {
103 | gradleVersion = '2.2'
104 | }
105 |
106 | class Version {
107 | int major, minor = 0, revision = 0
108 | boolean snapshot
109 | String stage
110 |
111 | String toString() {
112 | "$major.$minor.$revision${stage ? '.' + stage : ''}${snapshot ? '-SNAPSHOT' : ''}"
113 | }
114 | }
115 |
116 |
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
176 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/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 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.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 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 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/README.md:
--------------------------------------------------------------------------------
1 | # jHiccup
2 | [](https://gitter.im/giltene/jHiccup?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3 | ----------------------------------------------------------------------------
4 |
5 | Written by Gil Tene of Azul Systems, and released to the public domain
6 | as explained at http://creativecommons.org/publicdomain/zero/1.0
7 |
8 | ----------------------------------------------------------------------------
9 |
10 | Version: 2.0.5
11 | ----------------------------------------------------------------------------
12 | ----------------------------------------------------------------------------
13 |
14 | jHiccup is a 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 | jHiccup can be executed in one of three main ways:
20 |
21 | 1. It can be run as a Java agent (using: java -javaagent:jHiccup.jar)
22 |
23 | 2. It can be injected into a running application (using: jHiccup -p )
24 |
25 | 3. It can also be run using a convenient wrapper command for your
26 | existing java application (using: jHiccup java myProg ...)
27 |
28 | ----------------------------------------------------------------------------
29 |
30 | # Using jHiccup as a Java agent:
31 |
32 | jHiccup is most often used as a java agent. this is useful for platforms and
33 | environments where a java agent is simpler to integrate into launch scripts,
34 | or in environments where using the bash jHiccup wrapper script is not practical
35 | (e.g. Windows, and environments where java is not directly launched from
36 | the command line).
37 |
38 | jHiccup.jar can be used as a java agent using the following launch syntax:
39 |
40 | % java -javaagent:jHiccup.jar MyProgram
41 |
42 | or
43 |
44 | % java -javaagent:jHiccup.jar="" MyProgram.jar -a -b -c
45 |
46 | You can find the available options for the Java agent mode by running:
47 |
48 | % java -javaagent:jHiccup.jar="-h"
49 |
50 | Here is a Java agent usage example with explicit parameters:
51 |
52 | % java -javaagent:jHiccup.jar="-d 0 -i 1000 -l hiccuplog -c" MyProgram.jar -a -b -c
53 |
54 | This example will record hiccups experienced during the running of MyProgram.jar
55 | in log file hiccuplog, while at the same time recording the hiccups experienced by
56 | a control process running in a separate JVM in the log file c.hiccuplog.
57 | Measurement will start immediately upon startups (rather than the 30 second
58 | default delay), and interval data will be records every 1 second (rather than the
59 | default 5 seconds).
60 |
61 | Useful java agent related notes:
62 |
63 | Note 1: When used as a java agent, jHiccup will treat spaces, commas, and
64 | semicolons as delimiting characters ([ ,;]+). For example, the option string
65 | "-d 0 -i 1000" is equivalent to the option string "-d,0,-i,1000". This is
66 | useful for environments where placing space delimiters into quoted strings
67 | is difficult or confusing.
68 |
69 | Note 2: I find that a common way people add jHiccup as a java agent is by using
70 | the _JAVA_OPTIONS environment variable. This often allows one to add the jHiccup
71 | java agent without significant launch script surgery. For example:
72 |
73 | export _JAVA_OPTIONS='-javaagent:/path/to/jHiccup/target/jHiccup.jar="-d 20000 -i 1000"'
74 |
75 | ----------------------------------------------------------------------------
76 |
77 | # Reading and processing the jHiccup log with jHiccupLogProcessor:
78 |
79 | jHiccup logs hiccup information in a histogram log (see HdrHistogram.org).
80 | This histogram log contains a full, high fidelity histogram of all collected
81 | result sin each interval, in a highly compressed form (typically using only
82 | ~200-400 bytes per interval). However, other than the timestamp and maximum
83 | hiccup magnitude found in the given interval, the rest of the log line for
84 | each interval is not human readable (it is a base64 encoding of a compressed
85 | HdrHistogram).
86 |
87 | To translate the jHiccup log file to a more human-readable form (identical to
88 | the form that jHiccup versions before jHiccup 2.0 had used), the jHiccupLogProcessor
89 | utility is provided. In it's simplest form, this utility can be used as such
90 |
91 | % jHiccupLogProcessor -i mylog.hlog -o mylog
92 |
93 | Which will produce log file mylog and mylog.hgrm containing a human readable
94 | interval log (with selcted percentiles in each interval), as well as a human
95 | readable histogram percentile distribution log.
96 |
97 | jHiccupLogProcessor can also be used to produce log files for an arbitrary
98 | section of the jHiccup log, by using the optional -start and -end parameters.
99 |
100 | See jHiccupLogProcessor -h for more details.
101 |
102 | ----------------------------------------------------------------------------
103 |
104 | # Launching jHiccup by attaching it to existing, running application:
105 |
106 | The jHiccup agent can be injected into a live, running Java application
107 | if the environment supports the java attach API (which is typically available
108 | in java environments running Java SE 6 or later).
109 |
110 | $ jHiccup -p
111 |
112 | NOTE: In order to attach to a running java application, the running
113 | application needs to have ${JAVA_HOME}/lib/tools.jar in it's classpath.
114 | While this is commonly the case already for many IDE and desktop environments,
115 | and for environments that involve or enable other attachable agents (such as
116 | profilers), you may find that it is not included in your application's
117 | classpath, and that it needs to be added if attaching jHiccup at runtime
118 | is needed (launching jHiccup as a Java agent per the below may be a good
119 | alternative).
120 |
121 | ----------------------------------------------------------------------------
122 |
123 | # Running jHiccup using the Wrapper Script form:
124 |
125 | In the wrapper script form, all it takes is adding the word "jHiccup" in
126 | front of whatever the java invocation command line is.
127 |
128 | For example, if your program were normally executed as:
129 |
130 | java MyProgram -a -b -c
131 |
132 | The launch line would become:
133 |
134 | jHiccup java MyProgram -a -b -c
135 |
136 | or, for a program launched with:
137 |
138 | /usr/bin/java -jar MyProgram.jar -a -b -c
139 |
140 | The launch line would become:
141 |
142 | jHiccup /usr/bin/java -jar MyProgram.jar -a -b -c
143 |
144 | or, to override the defaults by making the recording start delay 60 seconds
145 | and log to hlog, it would become:
146 |
147 | jHiccup -d 60000 -l hlog /usr/bin/java -jar MyProgram.jar -a -b -c
148 |
149 | The jar file also includes a simple "Idle" class to facilitate sanity checks
150 | without an external program. Here is a simple sanity test example: jHiccup
151 | with a 4 sec delay on recording start, wrapping an Idle run that does nothing
152 | for 30 seconds and exits:
153 |
154 | % jHiccup -d 4000 /usr/bin/java org.jhiccup.Idle -t 30000
155 |
156 | [Run jHiccup -h, or see comment in jHiccup script for more details.]
157 |
158 | ----------------------------------------------------------------------------
159 |
160 | # Hiccup Charts: Plotting jHiccup results
161 |
162 | A jHiccupPlotter.xls Excel spreadsheet is included to conveniently
163 | plot jHiccup log files produced by jHiccupLogProcessor in "Hiccup Chart"form.
164 | To use the spreadsheet, load it into Excel, (make sure to enable macros),
165 | and follow the 2-step instructions in the main menu worksheet to automatically
166 | import the log files and produce the Hiccup Chart.
167 |
168 | Note that jHiccupPlotter.xls reads the log files produced by jHiccupLogProcessor,
169 | (the interval log and the .hgrm histogram percentile distribution log), and
170 | not the .hlog log format that jHiccup outputs directly.
171 |
172 | ----------------------------------------------------------------------------
173 |
174 | # Supported/Tested platforms:
175 |
176 | The jHiccup command is expected to work and has been tested on the following
177 | platforms:
178 | - Various Linux flavors (Tested on RHEL/CentOS 5.x and 6.x)
179 | - Mac OS X (tested on Lion, 10.7)
180 | - Windows with a Cygwin environment installed (tested on Windows 7)
181 | - Solaris (tested on both SPARC and x86)
182 |
183 | jHiccup.jar is expected to work as a java agent and has been tested on the
184 | following platforms:
185 | - Various Linux flavors (Tested on RHEL/CentOS 5.x and 6.x)
186 | - Mac OS X (tested on Lion, 10.7)
187 | - Windows standard command shell (tested on Windows 7)
188 | - Solaris (tested on both SPARC and x86)
189 |
190 | If you use jHiccup on other operating systems and setups, please report back
191 | on your experience so that we can expand the list.
192 |
193 | ----------------------------------------------------------------------------
194 |
195 | # Using a control process to concurrently record baseline idle load hiccups:
196 |
197 | It is often useful to compare the hiccup behavior experienced by a running
198 | application with a "control" hiccup level of an idle workload, running on
199 | the same system and at the same time as the observed application. To make
200 | such control measurement convenient, jHiccup supports a "-c" option that will
201 | launch a concurrently executing "control process" and will separately log
202 | hiccup information of an idle workload running on a separate jvm for the
203 | duration of the instrumented application run. When selected, the control
204 | process log file name will match those used for the launching application,
205 | followed with a ".c".
206 |
207 | For example:
208 |
209 | % jHiccup -l mylog -c /usr/bin/java -jar MyProgram.jar -a -b -c
210 |
211 | Will produce log file mylog detailing the hiccup behavior during the
212 | execution of MyProgram.jar, as well as a log file c.mylog detailing
213 | the hiccup behavior of an idle workload running on a separate jvm at
214 | the same time.
215 |
216 | ----------------------------------------------------------------------------
217 |
218 | # Log file name recognizes and fills in %pid and %date terms
219 |
220 | When a log file name is specified with the -l option, the terms %pid and
221 | %date will be filled in with the appropriate information. The default
222 | log file name setting is simply hiccup.%date.%pid.
223 |
224 | ----------------------------------------------------------------------------
225 |
226 | # Using jHiccup to process latency log files:
227 |
228 | jHiccup's HiccupMeter class supports a mode (-f) that will take latency
229 | input from a file instead of recording it. This is useful for producing
230 | jHiccup-style text and graphical output for recorded latency data collected
231 | by some other means.
232 |
233 | When provided to the -f option, an input file is expected to contain two
234 | white-space delimited values per line (in either integer or real number format),
235 | representing a time stamp and a measured latency, both in millisecond units.
236 |
237 | It's important to note that the default "expected interval between samples"
238 | resolution in jHiccup and HiccupMeter is 1 millisecond. When processing
239 | input files, it is imperative that an appropriate value be supplied to
240 | the -r option, and that this value correctly represent the expected interval
241 | between samples in the provided input file. HiccupMeter will use this
242 | parameter to determine whether additional, artificial values should be added
243 | to the histogram recording, between input samples that are farther apart in
244 | time than the expected interval specified to the -r option. This behavior
245 | corrects for "coordinated omission" situations (where long response times
246 | lead to "skipped" requests that would have typically correlated with "bad"
247 | response times). A "large" value (e.g. -r 100000) can easily be specified
248 | to avoid any correction of this situation.
249 |
250 | ----------------------------------------------------------------------------
251 |
252 | # Example: adding jHiccup to Tomcat runs:
253 |
254 | In Tomcat's catalina.sh script, replace the following line:
255 | exec "$_RUNJAVA" "$LOGGING_CONFIG" $JAVA_OPTS $CATALINA_OPTS
256 |
257 | with:
258 | exec "$JHICCUP_HOME/jHiccup" "$_RUNJAVA" "$LOGGING_CONFIG" $JAVA_OPTS $CATALINA_OPTS
259 |
260 | ----------------------------------------------------------------------------
261 |
262 | # Note: Use of HdrHistogram.
263 |
264 | jHiccup depends on and makes systemic use of HdrHistogram to collected and
265 | report on the statistical distribution of hiccups. This package includes an
266 | HdrHistogram.jar jar file to support this functionality. HdrHistogram sources,
267 | documentation, and a ready to use jar file can all be found on GitHub, at
268 | http://giltene.github.com/HdrHistogram
269 |
270 | ----------------------------------------------------------------------------
271 |
272 | # Building jHiccup:
273 |
274 | jHiccup can be (re)built from source files using Maven:
275 |
276 | % mvn package
277 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/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.5
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 30000 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_IntervalArg=
175 | readingIntervalArg=0
176 | JHICCUP_ResolutionArg=
177 | readingResolutionArg=0
178 | JHICCUP_LognameArg=
179 | readingLognnameArg=0
180 | JHICCUP_PidOfProcessToAttacheToArg=
181 | readingPidOfProcessToAttacheToArg=0
182 |
183 | verboseOutput=
184 | logFormatCsv=
185 | startTimeAtZero=
186 | JHiccupArgs_parse_error=
187 | JHICCUP_ControlProcessFlag=
188 |
189 | for var in $JHiccupArgs; do
190 | if [ $readingDelayArg -eq 1 ]; then
191 | JHICCUP_DelayArg=$var
192 | readingDelayArg=0
193 | elif [ $readingIntervalArg -eq 1 ]; then
194 | JHICCUP_IntervalArg=$var
195 | readingIntervalArg=0
196 | elif [ $readingResolutionArg -eq 1 ]; then
197 | JHICCUP_ResolutionArg=$var
198 | readingResolutionArg=0
199 | elif [ $readingLognnameArg -eq 1 ]; then
200 | JHICCUP_LognameArg=$var
201 | readingLognnameArg=0
202 | elif [ $readingPidOfProcessToAttacheToArg -eq 1 ]; then
203 | JHICCUP_PidOfProcessToAttacheToArg=$var
204 | readingPidOfProcessToAttacheToArg=0
205 | elif [ $var = "-d" ]; then
206 | readingDelayArg=1
207 | elif [ $var = "-i" ]; then
208 | readingIntervalArg=1
209 | elif [ $var = "-r" ]; then
210 | readingResolutionArg=1
211 | elif [ $var = "-l" ]; then
212 | readingLognnameArg=1
213 | elif [ $var = "-p" ]; then
214 | readingPidOfProcessToAttacheToArg=1
215 | elif [ $var = "-c" ]; then
216 | JHICCUP_ControlProcessFlag=1
217 | elif [ $var = "-0" ]; then
218 | startTimeAtZero=1
219 | elif [ $var = "-o" ]; then
220 | logFormatCsv=1
221 | elif [ $var = "-v" ]; then
222 | verboseOutput=1
223 | echo jHiccup version $JHICCUP_Version
224 | else
225 | JHiccupArgs_parse_error=1
226 | fi
227 | done
228 |
229 | # Should not have both a java command and a -p option:
230 | if [ $PARSED_BinJava ]; then
231 | if [ $JHICCUP_PidOfProcessToAttacheToArg ]; then
232 | JHiccupArgs_parse_error=1
233 | fi
234 | fi
235 |
236 | if [ $JHiccupArgs_parse_error ]; then
237 | echo $PARSED_SCRIPT $@
238 | echo jHiccup version $JHICCUP_Version
239 | echo "Usage:"
240 | echo " jHiccup [-d startupDelayMsec] [-i recordingIntervalMsec] [-l logname]"
241 | echo " [-r sampleResolutionMsec] [-c] [-p pidOfProcessToAttachTo]"
242 | echo " or:"
243 | echo " jHiccup [-d startupDelayMsec] [-i recordingIntervalMsec] [-l logname]"
244 | echo " [-r sampleResolutionMsec] [-c] "
245 | echo "Where:"
246 | echo " -l logname Sets the log files to and .hgrm"
247 | echo " (default is \"hiccup.yymmdd.hhmm.pid\") "
248 | echo " (replaces occurrences of %pid and %date with appropriate info)"
249 | echo " -o Output log files in CSV format"
250 | echo " -c Concurrently start a control process to record hiccups"
251 | echo " experienced by an Idle load running on a separate jvm"
252 | echo " in log files .c and .c.hgrm"
253 | echo " -p pidOfProcessToAttachTo Attach to the process with given pid and inject jHiccup as an agent"
254 | echo " (no default)"
255 | echo " -d startupDelayMsec Sets the delay, in milliseconds before sampling starts"
256 | echo " (default 30000 for non-attach (-p) runs, 0 for attach (-p) runs)"
257 | echo " -0 Start logfile timestamps at 0 (as opposed to JVM uptime at start point)"
258 | echo " (default off)"
259 | echo " -i recordingIntervalMsec Sets the reporting interval in milliseconds"
260 | echo " (default 5000)"
261 | echo " -r sampleResolutionMsec Sets the sampling resolution in milliseconds"
262 | echo " (default 1)"
263 | echo " -v Verbose output"
264 | exit -1
265 | fi
266 |
267 | JHICCUP_Options=""
268 |
269 | if [ $JHICCUP_DelayArg ]; then
270 | JHICCUP_Options="${JHICCUP_Options}-d $JHICCUP_DelayArg "
271 | fi
272 |
273 | if [ $JHICCUP_IntervalArg ]; then
274 | JHICCUP_Options="${JHICCUP_Options}-i $JHICCUP_IntervalArg "
275 | fi
276 |
277 | if [ $JHICCUP_ResolutionArg ]; then
278 | JHICCUP_Options="${JHICCUP_Options}-r $JHICCUP_ResolutionArg "
279 | fi
280 |
281 | if [ $JHICCUP_LognameArg ]; then
282 | JHICCUP_Options="${JHICCUP_Options}-l $JHICCUP_LognameArg "
283 | fi
284 |
285 | if [ $startTimeAtZero ]; then
286 | JHICCUP_Options="${JHICCUP_Options}-0 "
287 | fi
288 |
289 | if [ $logFormatCsv ]; then
290 | JHICCUP_Options="${JHICCUP_Options}-o "
291 | fi
292 |
293 | if [ $JHICCUP_ControlProcessFlag ]; then
294 | JHICCUP_Options="${JHICCUP_Options}-c "
295 | fi
296 |
297 | if [ $verboseOutput ]; then
298 | JHICCUP_Options="${JHICCUP_Options}-v "
299 | fi
300 |
301 | # Deal with Windows/cygwin path normalization syntax needs:
302 | # Key Assumption: only cygwin/Windows installations will have a cygpath command...
303 | cygpath -w $JHICCUP_JAR_FILE &> /dev/null
304 | if [ $? -eq 0 ] ; then
305 | # if using cygwin, use valid windows-style classpath
306 | JHICCUP_JAR_FILE=`cygpath -w $JHICCUP_JAR_FILE`
307 | echo Windows path for hiccup jar file is $JHICCUP_JAR_FILE
308 | fi
309 |
310 | if [ $JHICCUP_PidOfProcessToAttacheToArg ]; then
311 | JHICCUP_Options="${JHICCUP_Options}-j $JHICCUP_JAR_FILE -p $JHICCUP_PidOfProcessToAttacheToArg "
312 |
313 | #
314 | # Prepare and execute attach command:
315 | #
316 |
317 | CMD="$JAVA_HOME/bin/java -cp $JAVA_HOME/lib/tools.jar:$JHICCUP_JAR_FILE org.jhiccup.HiccupMeterAttacher $JHICCUP_Options"
318 | if [ $verboseOutput ]; then
319 | echo jHiccup executing: $CMD
320 | fi
321 | exec $JAVA_HOME/bin/java -cp $JAVA_HOME/lib/tools.jar:$JHICCUP_JAR_FILE org.jhiccup.HiccupMeterAttacher $JHICCUP_Options
322 | fi
323 |
324 | #
325 | # Prepare and execute command:
326 | #
327 | CMD="$PARSED_BinJava -javaagent:$JHICCUP_JAR_FILE=\"$JHICCUP_Options\" $PARSED_JavaArgs $PARSED_AppArgs"
328 | if [ $verboseOutput ]; then
329 | echo jHiccup executing: $CMD
330 | fi
331 | exec $PARSED_BinJava -javaagent:$JHICCUP_JAR_FILE="$JHICCUP_Options" $PARSED_JavaArgs $PARSED_AppArgs
332 | #exec $CMD
333 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/jHiccup.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/deps/jHiccup-2.0.5/jHiccup.jar
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/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 | function readlink_f () { _=`pwd`; cd `dirname $1` && echo `pwd` && cd $_; }
47 | INSTALLED_PATH=$(readlink_f $0)
48 |
49 | # Check if running from unpacked distribution archive by assuming jHiccup.jar
50 | # in the same directory as this script. If not, try to search in target/ directory
51 | # (running from the source repository build).
52 | JHICCUP_JAR_FILE=$INSTALLED_PATH/jHiccup.jar
53 | if [ ! -f $JHICCUP_JAR_FILE ] ; then
54 | JHICCUP_JAR_FILE=$INSTALLED_PATH/target/jHiccup.jar
55 | fi
56 |
57 | JAVA_BIN=`which java`
58 |
59 | if [ $JAVA_HOME ]; then
60 | JAVA_CMD=$JAVA_HOME/bin/java
61 | elif [ $JAVA_BIN ]; then
62 | JAVA_CMD=$JAVA_BIN
63 | else
64 | echo "For this command to run, either $JAVA_HOME must be set, or java must be in the path."
65 | exit 1
66 | fi
67 |
68 | #
69 | # Parse original java execution arguments:
70 | #
71 | # At this point, we should have valid $PARSED_BinJava, $PARSED_JavaArgs, $PARSED_AppArgs:
72 | #echo PARSED_BinJava = "$PARSED_BinJava"
73 | #echo PARSED_JavaArgs = "$PARSED_JavaArgs"
74 | #echo PARSED_AppArgs = "$PARSED_AppArgs"
75 |
76 | # Deal with Windows/cygwin path normalization syntax needs:
77 | # Key Assumption: only cygwin/Windows installations will have a cygpath command...
78 | cygpath -w $JHICCUP_JAR_FILE &> /dev/null
79 | if [ $? -eq 0 ] ; then
80 | # if using cygwin, use valid windows-style classpath
81 | JHICCUP_JAR_FILE=`cygpath -w $JHICCUP_JAR_FILE`
82 | echo Windows path for hiccup jar file is $JHICCUP_JAR_FILE
83 | fi
84 |
85 | exec $JAVA_CMD -cp $JHICCUP_JAR_FILE org.jhiccup.internal.hdrhistogram.HistogramLogProcessor $@
86 | #exec $CMD
87 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/jHiccupPlotter.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/deps/jHiccup-2.0.5/jHiccupPlotter.xls
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jhiccup
6 | jHiccup
7 | 2.0.5
8 |
9 | jHiccup
10 |
11 | http://jhiccup.org
12 |
13 |
14 | jHiccup is a 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.5
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.6
68 | 1.6
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 |
155 |
156 | package
157 |
158 | jarjar
159 |
160 |
161 |
162 | org.hdrhistogram:HdrHistogram
163 |
164 |
165 |
166 | org.HdrHistogram.**
167 | org.jhiccup.internal.hdrhistogram.@1
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | com.google.code.maven-replacer-plugin
177 | maven-replacer-plugin
178 | 1.4.0
179 |
180 |
181 | process-sources
182 |
183 | replace
184 |
185 |
186 |
187 |
188 | ${version.template.file}
189 | ${version.file}
190 |
191 |
192 | \$BUILD_TIME\$
193 | ${maven.build.timestamp}
194 |
195 |
196 | \$VERSION\$
197 | ${project.version}
198 |
199 |
200 |
201 |
202 |
203 |
204 | maven-assembly-plugin
205 | 2.4
206 |
207 |
208 | src/main/assembly/dist.xml
209 |
210 |
211 |
212 |
213 | package
214 |
215 | single
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | junit
226 | junit
227 | 4.10
228 | test
229 |
230 |
231 | org.hdrhistogram
232 | HdrHistogram
233 | 2.1.5
234 |
235 |
236 |
237 |
238 |
239 |
240 | toosjar-profile
241 |
242 |
243 | ${java.home}/../lib/tools.jar
244 |
245 |
246 |
247 | ${java.home}/../lib/tools.jar
248 |
249 |
250 |
251 |
252 | com.sun
253 | tools
254 |
255 | 99.9.9
256 | system
257 | ${java.home}/../lib/tools.jar
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/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 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/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.pidOfProcessToAttachTo +
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 | System.out.println(ex);
53 | System.exit(1);
54 | } catch (AttachNotSupportedException ex) {
55 | System.out.println(ex);
56 | System.exit(1);
57 | } catch (AgentInitializationException ex) {
58 | System.out.println(ex);
59 | System.exit(1);
60 | } catch ( AgentLoadException ex) {
61 | System.out.println(ex);
62 | System.exit(1);
63 | }
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/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]);
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 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/src/main/java/org/jhiccup/Version.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 | public final class Version {
11 | public static final String version="2.0.5";
12 | public static final String build_time="2015-07-28T17:46:24Z";
13 | }
14 |
--------------------------------------------------------------------------------
/deps/jHiccup-2.0.5/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 |
--------------------------------------------------------------------------------
/doc/application.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/application.png
--------------------------------------------------------------------------------
/doc/baseline-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/baseline-chart.png
--------------------------------------------------------------------------------
/doc/baseline_vs_no_workqueue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/baseline_vs_no_workqueue.png
--------------------------------------------------------------------------------
/doc/isolcpus-chart-log-scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/isolcpus-chart-log-scale.png
--------------------------------------------------------------------------------
/doc/isolcpus-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/isolcpus-chart.png
--------------------------------------------------------------------------------
/doc/lstopo-cpus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/lstopo-cpus.png
--------------------------------------------------------------------------------
/doc/no_watchdog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/no_watchdog.png
--------------------------------------------------------------------------------
/doc/performance-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/performance-chart.png
--------------------------------------------------------------------------------
/doc/pinned-thread-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/pinned-thread-chart.png
--------------------------------------------------------------------------------
/doc/reduced_interrupts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/doc/reduced_interrupts.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epickrram/perf-workshop/c15f2d19ef82a0b2db0dc41c8e8b5e1732d0bd99/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Feb 09 13:53:35 GMT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'perf-workshop'
2 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/UberMain.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.beust.jcommander.JCommander;
21 | import com.epickrram.workshop.perf.app.AppMain;
22 | import com.epickrram.workshop.perf.config.CommandLineArgs;
23 | import com.epickrram.workshop.perf.reporting.PostProcess;
24 | import com.epickrram.workshop.perf.setup.InputGenerator;
25 |
26 | public final class UberMain
27 | {
28 | public static void main(final String[] args) throws Exception
29 | {
30 | final CommandLineArgs commandLineArgs = new CommandLineArgs();
31 | final JCommander jCommander = new JCommander(commandLineArgs);
32 | jCommander.parse(args);
33 | if(commandLineArgs.isHelp())
34 | {
35 | jCommander.usage();
36 | System.exit(0);
37 | }
38 |
39 | InputGenerator.main(args);
40 | AppMain.main(args);
41 | PostProcess.main(args);
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/AppMain.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.beust.jcommander.JCommander;
21 | import com.epickrram.workshop.perf.app.jitter.Spinners;
22 | import com.epickrram.workshop.perf.app.message.Packet;
23 | import com.epickrram.workshop.perf.app.processors.Accumulator;
24 | import com.epickrram.workshop.perf.app.processors.InputReader;
25 | import com.epickrram.workshop.perf.app.processors.Journaller;
26 | import com.epickrram.workshop.perf.config.CommandLineArgs;
27 | import com.epickrram.workshop.perf.config.Overrides;
28 | import com.epickrram.workshop.perf.support.SpinLoopHintBusySpinWaitStrategy;
29 | import com.lmax.disruptor.TimeoutException;
30 | import com.lmax.disruptor.dsl.Disruptor;
31 | import com.lmax.disruptor.dsl.ProducerType;
32 | import org.HdrHistogram.Histogram;
33 |
34 | import java.util.Date;
35 | import java.util.concurrent.TimeUnit;
36 |
37 | import static com.epickrram.workshop.perf.app.processors.EventHandlerAdapter.wrap;
38 | import static com.epickrram.workshop.perf.app.processors.ThreadAffinityEventHandler.runOnCpus;
39 | import static com.epickrram.workshop.perf.support.DaemonThreadFactory.DAEMON_THREAD_FACTORY;
40 | import static com.epickrram.workshop.perf.support.Histograms.HISTOGRAMS;
41 | import static com.epickrram.workshop.perf.support.SystemNanoTimer.SYSTEM_NANO_TIMER;
42 | import static com.epickrram.workshop.perf.support.Threads.THREADS;
43 | import static java.util.Arrays.setAll;
44 | import static java.util.concurrent.Executors.newCachedThreadPool;
45 |
46 | public final class AppMain
47 | {
48 | public static void main(final String[] args) throws Exception
49 | {
50 | final CommandLineArgs commandLineArgs = new CommandLineArgs();
51 | new JCommander(commandLineArgs).parse(args);
52 |
53 | final Disruptor packetDisruptor =
54 | new Disruptor<>(new Packet.Factory(commandLineArgs.getRecordLength()), commandLineArgs.getBufferSize(),
55 | newCachedThreadPool(DAEMON_THREAD_FACTORY), ProducerType.SINGLE, new SpinLoopHintBusySpinWaitStrategy());
56 |
57 | final Overrides overrides = new Overrides(commandLineArgs);
58 | overrides.init();
59 |
60 | final Journaller journaller = new Journaller(SYSTEM_NANO_TIMER, commandLineArgs, overrides.enableJournaller());
61 | journaller.init();
62 |
63 | final Histogram[] messageTransitTimeHistograms = new Histogram[commandLineArgs.getNumberOfIterations()];
64 | setAll(messageTransitTimeHistograms, HISTOGRAMS::createHistogramForArray);
65 | final Histogram[] interMessageTimeHistograms = new Histogram[commandLineArgs.getNumberOfIterations()];
66 | setAll(interMessageTimeHistograms, HISTOGRAMS::createHistogramForArray);
67 |
68 | packetDisruptor.handleEventsWith(
69 | runOnCpus(wrap(new Accumulator(messageTransitTimeHistograms, interMessageTimeHistograms, SYSTEM_NANO_TIMER, commandLineArgs)::process),
70 | "Accumulator", overrides.getAccumulatorThreadAffinity()),
71 | runOnCpus(wrap(journaller::process), "Journaller", overrides.getJournallerThreadAffinity()));
72 |
73 | packetDisruptor.start();
74 |
75 | final InputReader inputReader = new InputReader(packetDisruptor.getRingBuffer(), SYSTEM_NANO_TIMER, commandLineArgs);
76 |
77 | if(commandLineArgs.runSpinners())
78 | {
79 | System.out.println("Starting spinner threads to perturb the system");
80 | Spinners.SPINNERS.start();
81 | }
82 |
83 | System.out.println("Starting replay at " + new Date());
84 |
85 | final Thread thread = DAEMON_THREAD_FACTORY.newThread(THREADS.runOnCpu(inputReader::processFiles,
86 | overrides.getProducerThreadAffinity()));
87 | thread.start();
88 |
89 | try
90 | {
91 | thread.join();
92 | System.out.println("Finished replay at " + new Date());
93 | packetDisruptor.shutdown(1, TimeUnit.MINUTES);
94 | }
95 | catch (TimeoutException e)
96 | {
97 | throw new RuntimeException("Consumers did not process remaining events within timeout", e);
98 | }
99 | finally
100 | {
101 | Spinners.SPINNERS.stop();
102 | packetDisruptor.halt();
103 | }
104 |
105 | System.out.println("Pausing for 10 seconds...");
106 | THREADS.sleep(10L, TimeUnit.SECONDS);
107 | }
108 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/jitter/Spinners.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.jitter;
2 |
3 | import com.epickrram.workshop.perf.support.Threads;
4 |
5 | import java.util.concurrent.ExecutorService;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | import static com.epickrram.workshop.perf.support.DaemonThreadFactory.DAEMON_THREAD_FACTORY;
9 | import static java.util.concurrent.Executors.newFixedThreadPool;
10 |
11 | public enum Spinners
12 | {
13 | SPINNERS;
14 |
15 | private static final long BUSY_SPIN_PERIOD_NANOS = TimeUnit.MILLISECONDS.toNanos(3L);
16 | private static final long SLEEP_PERIOD_NANOS = TimeUnit.MILLISECONDS.toNanos(30L);
17 |
18 | private volatile boolean running = false;
19 |
20 | public void start()
21 | {
22 | running = true;
23 | final int processors = Runtime.getRuntime().availableProcessors();
24 | final ExecutorService executorService = newFixedThreadPool(processors, DAEMON_THREAD_FACTORY);
25 | for(int i = 0; i < processors; i++)
26 | {
27 | executorService.submit(new Spinner());
28 | }
29 | }
30 |
31 | public void stop()
32 | {
33 | running = false;
34 | }
35 |
36 | private class Spinner implements Runnable
37 | {
38 | private long counter = 0;
39 |
40 | @Override
41 | public void run()
42 | {
43 | while(running)
44 | {
45 | final long stopSpinningAt = System.nanoTime() + BUSY_SPIN_PERIOD_NANOS;
46 | while(System.nanoTime() < stopSpinningAt)
47 | {
48 | counter++;
49 | }
50 |
51 | Threads.THREADS.sleep(SLEEP_PERIOD_NANOS, TimeUnit.NANOSECONDS);
52 | }
53 | }
54 |
55 | public long getCounter()
56 | {
57 | return counter;
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/message/JournalEntry.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.message;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import java.io.IOException;
21 | import java.nio.ByteBuffer;
22 | import java.nio.channels.ReadableByteChannel;
23 | import java.nio.channels.WritableByteChannel;
24 |
25 | import static java.nio.ByteBuffer.allocateDirect;
26 |
27 | public final class JournalEntry
28 | {
29 | public static final int ENTRY_MAGIC_NUMBER = 0x55555555;
30 | public static final int ENTRY_SIZE = 4 + 8 + 8 + 8 + 4;
31 |
32 | private final ByteBuffer buffer = allocateDirect(ENTRY_SIZE);
33 |
34 | public void set(final long publisherNanoTime, final long journallerNanoTime, final long deltaNanos, final int sequenceInFile)
35 | {
36 | buffer.clear();
37 | buffer.putInt(ENTRY_MAGIC_NUMBER);
38 | buffer.putLong(publisherNanoTime);
39 | buffer.putLong(journallerNanoTime);
40 | buffer.putLong(deltaNanos);
41 | buffer.putInt(sequenceInFile);
42 | buffer.flip();
43 | }
44 |
45 | public void writeTo(final WritableByteChannel destination) throws IOException
46 | {
47 | validateForWriting();
48 | destination.write(buffer);
49 | }
50 |
51 | public void readFrom(final ReadableByteChannel source) throws IOException
52 | {
53 | buffer.clear();
54 | source.read(buffer);
55 | buffer.flip();
56 | }
57 |
58 | public long getPublisherNanoTime()
59 | {
60 | return buffer.getLong(4);
61 | }
62 |
63 | public long getJournallerNanoTime()
64 | {
65 | return buffer.getLong(12);
66 | }
67 |
68 | public long getDeltaNanos()
69 | {
70 | return buffer.getLong(20);
71 | }
72 |
73 | public int getSequenceInFile()
74 | {
75 | return buffer.getInt(28);
76 | }
77 |
78 | public boolean canRead()
79 | {
80 | return buffer.getInt(0) == ENTRY_MAGIC_NUMBER && buffer.limit() == ENTRY_SIZE;
81 | }
82 |
83 | private void validateForWriting()
84 | {
85 | if(buffer.position() != 0 || buffer.limit() != ENTRY_SIZE)
86 | {
87 | throw new IllegalStateException("Buffer is in inconsistent state for writing");
88 | }
89 | }
90 |
91 | @Override
92 | public String toString()
93 | {
94 | return "JournalEntry{" +
95 | "published: " + getPublisherNanoTime() +
96 | ", journalled: " + getJournallerNanoTime() +
97 | ", sequence: " + getSequenceInFile() +
98 | "}";
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/message/Packet.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.message;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.lmax.disruptor.EventFactory;
21 |
22 | import java.nio.ByteBuffer;
23 |
24 | import static java.nio.ByteBuffer.allocateDirect;
25 |
26 | public final class Packet
27 | {
28 | private final ByteBuffer payload;
29 | private boolean isLastInFile;
30 | private long sequence;
31 | private int sequenceInFile;
32 | private boolean isLastInStream;
33 | private long receivedNanoTime;
34 |
35 | private Packet(final int payloadLength)
36 | {
37 | this.payload = allocateDirect(payloadLength);
38 | }
39 |
40 | public void reset()
41 | {
42 | isLastInFile = false;
43 | isLastInStream = false;
44 | sequence = -1L;
45 | sequenceInFile = -1;
46 | receivedNanoTime = 0L;
47 | payload.clear();
48 | }
49 |
50 | public ByteBuffer getPayload()
51 | {
52 | return payload;
53 | }
54 |
55 | public boolean isLastInFile()
56 | {
57 | return isLastInFile;
58 | }
59 |
60 | public void setLastInFile(final boolean isLastInFile)
61 | {
62 | this.isLastInFile = isLastInFile;
63 | }
64 |
65 | public long getSequence()
66 | {
67 | return sequence;
68 | }
69 |
70 | public void setSequence(final long sequence)
71 | {
72 | this.sequence = sequence;
73 | }
74 |
75 | public int getSequenceInFile()
76 | {
77 | return sequenceInFile;
78 | }
79 |
80 | public void setSequenceInFile(final int sequenceInFile)
81 | {
82 | this.sequenceInFile = sequenceInFile;
83 | }
84 |
85 | public boolean isLastInStream()
86 | {
87 | return isLastInStream;
88 | }
89 |
90 | public void setLastInStream(final boolean isLastInStream)
91 | {
92 | this.isLastInStream = isLastInStream;
93 | }
94 |
95 | public long getReceivedNanoTime()
96 | {
97 | return receivedNanoTime;
98 | }
99 |
100 | public void setReceivedNanoTime(final long receivedNanoTime)
101 | {
102 | this.receivedNanoTime = receivedNanoTime;
103 | }
104 |
105 | public static final class Factory implements EventFactory
106 | {
107 | private final int payloadLength;
108 |
109 | public Factory(final int payloadLength)
110 | {
111 | this.payloadLength = payloadLength;
112 | }
113 |
114 | @Override
115 | public Packet newInstance()
116 | {
117 | return new Packet(payloadLength);
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/processors/Accumulator.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.processors;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.epickrram.workshop.perf.app.message.Packet;
21 | import com.epickrram.workshop.perf.config.CommandLineArgs;
22 | import com.epickrram.workshop.perf.support.NanoTimer;
23 | import org.HdrHistogram.Histogram;
24 | import org.HdrHistogram.HistogramLogWriter;
25 |
26 | import java.io.File;
27 | import java.io.FileNotFoundException;
28 | import java.io.PrintStream;
29 |
30 | import static com.epickrram.workshop.perf.support.Histograms.HISTOGRAMS;
31 |
32 | public final class Accumulator
33 | {
34 | public static final String TRANSIT_TIME_HISTOGRAM_QUALIFIER = "transit-time";
35 | public static final String INTER_MESSAGE_HISTOGRAM_QUALIFIER = "inter-message";
36 |
37 | private final Histogram[] messageTransitTimeHistograms;
38 | private final Histogram[] interMessageTimeHistograms;
39 | private final NanoTimer nanoTimer;
40 | private final CommandLineArgs commandLineArgs;
41 |
42 | private int streamNumber = 0;
43 | private long previousMessageNanoTime;
44 |
45 | private boolean setThreadName = false;
46 |
47 | public Accumulator(final Histogram[] messageTransitTimeHistograms, final Histogram[] interMessageTimeHistograms,
48 | final NanoTimer nanoTimer, final CommandLineArgs commandLineArgs)
49 | {
50 | this.messageTransitTimeHistograms = messageTransitTimeHistograms;
51 | this.interMessageTimeHistograms = interMessageTimeHistograms;
52 | this.nanoTimer = nanoTimer;
53 | this.commandLineArgs = commandLineArgs;
54 | }
55 |
56 | public void process(final Packet packet)
57 | {
58 | if (!setThreadName)
59 | {
60 | Thread.currentThread().setName("Accumulator");
61 | setThreadName = true;
62 | }
63 | final long nanoTime = nanoTimer.nanoTime();
64 | if(packet.getSequenceInFile() != 0)
65 | {
66 | final long deltaNanos = nanoTime - packet.getReceivedNanoTime();
67 | HISTOGRAMS.safeRecord(deltaNanos, messageTransitTimeHistograms[streamNumber]);
68 | HISTOGRAMS.safeRecord(nanoTime - previousMessageNanoTime, interMessageTimeHistograms[streamNumber]);
69 | }
70 |
71 | if(packet.isLastInFile())
72 | {
73 | streamNumber++;
74 | }
75 |
76 | if(packet.isLastInStream())
77 | {
78 | outputHistogram(mergeHistogramsAfterWarmupPeriod(messageTransitTimeHistograms), 0, TRANSIT_TIME_HISTOGRAM_QUALIFIER);
79 | outputHistogram(mergeHistogramsAfterWarmupPeriod(interMessageTimeHistograms), 0, INTER_MESSAGE_HISTOGRAM_QUALIFIER);
80 | }
81 |
82 | previousMessageNanoTime = nanoTime;
83 | }
84 |
85 | private Histogram mergeHistogramsAfterWarmupPeriod(final Histogram[] histograms)
86 | {
87 | final Histogram target = HISTOGRAMS.createHistogram();
88 | for (int i = 0; i < histograms.length; i++)
89 | {
90 | if(i > commandLineArgs.getNumberOfWarmups())
91 | {
92 | target.add(histograms[i]);
93 | }
94 | }
95 |
96 | return target;
97 | }
98 |
99 | private void outputHistogram(final Histogram histogram, final int streamNumber, final String qualifier)
100 | {
101 | try
102 | {
103 | try(final PrintStream printStream = new PrintStream(getHistogramOutputFile(commandLineArgs, streamNumber, qualifier)))
104 | {
105 | new HistogramLogWriter(printStream).outputIntervalHistogram(streamNumber, streamNumber + 1, histogram, 1d);
106 | }
107 | }
108 | catch (FileNotFoundException e)
109 | {
110 | throw new RuntimeException("Failed to write histogram", e);
111 | }
112 | }
113 |
114 | private static File getHistogramOutputFile(final CommandLineArgs commandLineArgs, final int streamNumber, final String qualifier)
115 | {
116 | return new File(commandLineArgs.getOutputDir(), "perf-workshop-" +
117 | "accumulator-histogram-" + qualifier + "-" + streamNumber + ".enc");
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/processors/EventHandlerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.processors;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.epickrram.workshop.perf.app.message.Packet;
21 | import com.lmax.disruptor.EventHandler;
22 |
23 | import java.util.function.Consumer;
24 |
25 | public final class EventHandlerAdapter implements EventHandler
26 | {
27 | private final Consumer consumer;
28 |
29 | private EventHandlerAdapter(final Consumer consumer)
30 | {
31 | this.consumer = consumer;
32 | }
33 |
34 | @Override
35 | public void onEvent(final Packet event, final long sequence, final boolean endOfBatch) throws Exception
36 | {
37 | consumer.accept(event);
38 | }
39 |
40 | public static EventHandler wrap(final Consumer consumer)
41 | {
42 | return new EventHandlerAdapter(consumer);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/processors/InputReader.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.processors;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.epickrram.workshop.perf.app.message.Packet;
21 | import com.epickrram.workshop.perf.config.CommandLineArgs;
22 | import com.epickrram.workshop.perf.support.NanoTimer;
23 | import com.lmax.disruptor.RingBuffer;
24 | import org.performancehints.SpinHint;
25 |
26 | import java.io.File;
27 | import java.io.IOException;
28 | import java.nio.MappedByteBuffer;
29 | import java.nio.channels.FileChannel;
30 | import java.util.Date;
31 | import java.util.concurrent.TimeUnit;
32 |
33 | import static com.epickrram.workshop.perf.support.Threads.THREADS;
34 | import static java.nio.channels.FileChannel.MapMode.READ_ONLY;
35 | import static java.nio.channels.FileChannel.open;
36 | import static java.nio.file.StandardOpenOption.READ;
37 |
38 | public final class InputReader
39 | {
40 | private final RingBuffer messageSink;
41 | private final NanoTimer nanoTimer;
42 | private final CommandLineArgs commandLineArgs;
43 | private final int numberOfIterations;
44 |
45 | public InputReader(final RingBuffer messageSink, final NanoTimer nanoTimer, final CommandLineArgs commandLineArgs)
46 | {
47 | this.messageSink = messageSink;
48 | this.nanoTimer = nanoTimer;
49 | this.commandLineArgs = commandLineArgs;
50 | this.numberOfIterations = commandLineArgs.getNumberOfIterations();
51 | }
52 |
53 | public void processFiles()
54 | {
55 | Thread.currentThread().setName("Producer");
56 | final File inputFile = new File(commandLineArgs.getInputFile());
57 | try (final FileChannel fileChannel = open(inputFile.toPath(), READ))
58 | {
59 | final MappedByteBuffer mappedFile = fileChannel.map(READ_ONLY, 0L, inputFile.length());
60 | mappedFile.load();
61 |
62 | System.out.println("Producer thread has pid: " + THREADS.getCurrentThreadId());
63 | for (int i = 0; i < numberOfIterations; i++)
64 | {
65 | if (i == commandLineArgs.getNumberOfWarmups())
66 | {
67 | System.out.println("Warm-up complete at " + new Date());
68 | System.out.println("Pausing for 10 seconds...");
69 | THREADS.sleep(10L, TimeUnit.SECONDS);
70 | System.out.println("Executing test at " + new Date());
71 | }
72 |
73 | processSingleFile(mappedFile, i == numberOfIterations - 1);
74 | }
75 | }
76 | catch (IOException e)
77 | {
78 | throw new RuntimeException("File operation failed", e);
79 | }
80 |
81 | }
82 |
83 | private void processSingleFile(final MappedByteBuffer mappedFile, final boolean isLastIteration)
84 | {
85 | for (int recordIndex = 0; recordIndex < commandLineArgs.getNumberOfRecords(); recordIndex++)
86 | {
87 | if (!messageSink.hasAvailableCapacity(1))
88 | {
89 | throw new RuntimeException("RingBuffer full!");
90 | }
91 | final long sequence = messageSink.next();
92 |
93 | try
94 | {
95 | final Packet packet = messageSink.get(sequence);
96 | packet.reset();
97 | packet.setSequenceInFile(recordIndex);
98 | packet.setSequence(sequence);
99 | final boolean isLastInFile = recordIndex == commandLineArgs.getNumberOfRecords() - 1;
100 | packet.setLastInFile(isLastInFile);
101 | packet.setReceivedNanoTime(nanoTimer.nanoTime());
102 | packet.setLastInStream(isLastInFile && isLastIteration);
103 | mappedFile.position(recordIndex * commandLineArgs.getRecordLength());
104 | mappedFile.limit(mappedFile.position() + commandLineArgs.getRecordLength());
105 | packet.getPayload().put(mappedFile);
106 | }
107 | finally
108 | {
109 | messageSink.publish(sequence);
110 | }
111 | introduceMessagePublishDelay();
112 | }
113 | }
114 |
115 | private void introduceMessagePublishDelay()
116 | {
117 | final long stopSpinningAt = System.nanoTime() + TimeUnit.MICROSECONDS.toNanos(10L);
118 | while (System.nanoTime() < stopSpinningAt)
119 | {
120 | SpinHint.spinLoopHint();
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/processors/Journaller.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.processors;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.epickrram.workshop.perf.config.CommandLineArgs;
21 | import com.epickrram.workshop.perf.app.message.JournalEntry;
22 | import com.epickrram.workshop.perf.app.message.Packet;
23 | import com.epickrram.workshop.perf.support.NanoTimer;
24 |
25 | import java.io.File;
26 | import java.io.IOException;
27 | import java.io.RandomAccessFile;
28 | import java.nio.ByteBuffer;
29 | import java.nio.channels.FileChannel;
30 |
31 | import static java.nio.ByteBuffer.allocateDirect;
32 |
33 | public final class Journaller
34 | {
35 | private final NanoTimer nanoTimer;
36 | private final CommandLineArgs commandLineArgs;
37 | private final JournalEntry journalEntry = new JournalEntry();
38 | private final boolean enabled;
39 |
40 | private FileChannel channel;
41 | private long position = 0;
42 | private boolean setThreadName = false;
43 |
44 | public Journaller(final NanoTimer nanoTimer, final CommandLineArgs commandLineArgs, final boolean enabled)
45 | {
46 | this.nanoTimer = nanoTimer;
47 | this.commandLineArgs = commandLineArgs;
48 | this.enabled = enabled;
49 | }
50 |
51 | public void init() throws IOException
52 | {
53 | System.out.println("Initialising journal files");
54 | final File journalFile = new File(commandLineArgs.getJournalFile());
55 | if(!journalFile.exists())
56 | {
57 | journalFile.createNewFile();
58 | }
59 | final RandomAccessFile randomAccessFile = new RandomAccessFile(commandLineArgs.getJournalFile(), "rw");
60 | channel = randomAccessFile.getChannel();
61 | channel.truncate(0L);
62 | final int totalNumberOfJournalEntries = commandLineArgs.getNumberOfRecords() * commandLineArgs.getNumberOfIterations();
63 | final long expectedMaximumJournalLength = JournalEntry.ENTRY_SIZE * totalNumberOfJournalEntries;
64 | channel.truncate(expectedMaximumJournalLength);
65 | final ByteBuffer nullEntry = allocateDirect(JournalEntry.ENTRY_SIZE);
66 |
67 | for(int i = 0; i < totalNumberOfJournalEntries; i++)
68 | {
69 | nullEntry.clear();
70 | channel.position(i * JournalEntry.ENTRY_SIZE);
71 | channel.write(nullEntry);
72 | }
73 |
74 | channel.force(false);
75 | }
76 |
77 | public void process(final Packet packet)
78 | {
79 | if (!setThreadName)
80 | {
81 | Thread.currentThread().setName("Journaller");
82 | }
83 | if(!enabled)
84 | {
85 | return;
86 | }
87 |
88 | final long journallerNanoTime = nanoTimer.nanoTime();
89 | final long deltaNanos = journallerNanoTime - packet.getReceivedNanoTime();
90 | writeEntry(packet.getReceivedNanoTime(), journallerNanoTime, deltaNanos, packet.getSequenceInFile());
91 |
92 | position += JournalEntry.ENTRY_SIZE;
93 |
94 | if(packet.isLastInStream())
95 | {
96 | closeChannel();
97 | }
98 | }
99 |
100 | private void writeEntry(final long publisherNanoTime, final long nanoTime, final long deltaNanos, final int sequenceInFile)
101 | {
102 | try
103 | {
104 | journalEntry.set(publisherNanoTime, nanoTime, deltaNanos, sequenceInFile);
105 | channel.position(position);
106 | journalEntry.writeTo(channel);
107 | }
108 | catch (IOException e)
109 | {
110 | throw new RuntimeException("File operation failed", e);
111 | }
112 | }
113 |
114 | private void closeChannel()
115 | {
116 | try
117 | {
118 | channel.close();
119 | }
120 | catch (IOException e)
121 | {
122 | throw new RuntimeException("File operation failed", e);
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/app/processors/ThreadAffinityEventHandler.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.app.processors;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.epickrram.workshop.perf.app.message.Packet;
21 | import com.lmax.disruptor.EventHandler;
22 | import com.lmax.disruptor.LifecycleAware;
23 |
24 | import static com.epickrram.workshop.perf.support.Threads.THREADS;
25 |
26 | public final class ThreadAffinityEventHandler implements EventHandler, LifecycleAware
27 | {
28 | private final EventHandler delegate;
29 | private final String processName;
30 | private final int[] cpuAffinity;
31 |
32 | private ThreadAffinityEventHandler(final EventHandler delegate, final String processName, final int... cpuAffinity)
33 | {
34 | this.delegate = delegate;
35 | this.processName = processName;
36 | this.cpuAffinity = cpuAffinity;
37 | }
38 |
39 | @Override
40 | public void onEvent(final Packet event, final long sequence, final boolean endOfBatch) throws Exception
41 | {
42 | delegate.onEvent(event, sequence, endOfBatch);
43 | }
44 |
45 | @Override
46 | public void onStart()
47 | {
48 | System.out.println(processName + " thread has pid: " + THREADS.getCurrentThreadId());
49 | if(cpuAffinity != null && cpuAffinity.length != 0)
50 | {
51 | THREADS.setCurrentThreadAffinity(cpuAffinity);
52 | }
53 | }
54 |
55 | public static EventHandler runOnCpus(final EventHandler delegate, final String processName, final int... cpus)
56 | {
57 | return new ThreadAffinityEventHandler(delegate, processName, cpus);
58 | }
59 |
60 | @Override
61 | public void onShutdown()
62 | {
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/config/CommandLineArgs.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.config;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.beust.jcommander.Parameter;
21 | import com.epickrram.workshop.perf.reporting.ReportFormat;
22 |
23 | import java.io.File;
24 | import java.util.Arrays;
25 | import java.util.EnumSet;
26 | import java.util.List;
27 | import java.util.Set;
28 |
29 | import static java.lang.System.getProperty;
30 | import static java.util.Arrays.asList;
31 |
32 | public final class CommandLineArgs
33 | {
34 | private final long executionTimestamp = System.currentTimeMillis();
35 |
36 | @Parameter(names = "-i", description = "number of iterations")
37 | private int numberOfIterations = 20;
38 | @Parameter(names = "-w", description = "number of warm-ups")
39 | private int numberOfWarmups = 10;
40 | @Parameter(names = "-n", description = "number of records per input file")
41 | private int numberOfRecords = 10000;
42 | @Parameter(names = "-l", description = "length of record (bytes)")
43 | private int recordLength = 64;
44 | @Parameter(names = "-f", description = "data file (default: /tmp/perf-workshop-input.bin)")
45 | private String inputFile = getTmpDirectory() + File.separator + "perf-workshop-input.bin";
46 | @Parameter(names = "-b", description = "buffer size (must be a power of two)")
47 | private int bufferSize = 524288;
48 | @Parameter(names = "-j", description = "journal file (default: /tmp/perf-workshop-output.jnl)")
49 | private String journalFile = getTmpDirectory() + File.separator + "perf-workshop-output.jnl";
50 | @Parameter(names = "-d", description = "output dir (default: /tmp)")
51 | private String outputDir = getTmpDirectory();
52 | @Parameter(names = "-o", description = "overrides file (default: /tmp/perf-workshop.properties)")
53 | private String overrideFile = getTmpDirectory() + File.separator + "perf-workshop.properties";
54 | @Parameter(names = "-r", description = "report format", variableArity = true)
55 | private List reportFormats = asList(ReportFormat.LONG.name());
56 | @Parameter(names = "-s", description = "run busy spinning threads to perturb system")
57 | private boolean runSpinners = false;
58 | @Parameter(names = "-t", description = "test label")
59 | private String testLabel = Long.toString(executionTimestamp);
60 | @Parameter(names = "-h", description = "print help and exit", help = true)
61 | private boolean help;
62 |
63 | public int getBufferSize()
64 | {
65 | return bufferSize;
66 | }
67 |
68 | public String getInputFile()
69 | {
70 | return inputFile;
71 | }
72 |
73 | public int getNumberOfIterations()
74 | {
75 | return numberOfIterations;
76 | }
77 |
78 | public int getNumberOfWarmups()
79 | {
80 | return numberOfWarmups;
81 | }
82 |
83 | public int getNumberOfRecords()
84 | {
85 | return numberOfRecords;
86 | }
87 |
88 | public int getRecordLength()
89 | {
90 | return recordLength;
91 | }
92 |
93 | public String getJournalFile()
94 | {
95 | return journalFile;
96 | }
97 |
98 | public String getOutputDir()
99 | {
100 | return outputDir;
101 | }
102 |
103 | public boolean runSpinners()
104 | {
105 | return runSpinners;
106 | }
107 |
108 | private static String getTmpDirectory()
109 | {
110 | return getProperty("java.io.tmpdir");
111 | }
112 |
113 | public String getOverrideFile()
114 | {
115 | return overrideFile;
116 | }
117 |
118 | public String getTestLabel()
119 | {
120 | return testLabel;
121 | }
122 |
123 | public boolean isHelp()
124 | {
125 | return help;
126 | }
127 |
128 | public Set getReportFormats()
129 | {
130 | final EnumSet formats = EnumSet.noneOf(ReportFormat.class);
131 | for (final String reportFormat : reportFormats)
132 | {
133 | formats.add(parseReportFormat(reportFormat));
134 | }
135 | return formats;
136 | }
137 |
138 | private ReportFormat parseReportFormat(final String reportFormat)
139 | {
140 | try
141 | {
142 | return ReportFormat.valueOf(reportFormat);
143 | }
144 | catch(final RuntimeException e)
145 | {
146 | throw new IllegalArgumentException("Unable to parse report format, must be one of: " +
147 | Arrays.toString(ReportFormat.values()));
148 | }
149 | }
150 |
151 | public long getExecutionTimestamp()
152 | {
153 | return executionTimestamp;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/config/Overrides.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.config;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import java.io.File;
21 | import java.io.FileReader;
22 | import java.io.FileWriter;
23 | import java.io.IOException;
24 | import java.util.List;
25 | import java.util.Properties;
26 |
27 | import static java.lang.String.format;
28 | import static java.util.Arrays.stream;
29 | import static java.util.stream.Collectors.toList;
30 |
31 | public final class Overrides
32 | {
33 | private static final String PROPERTY_PREFIX = "perf.workshop.";
34 | private static final String JOURNALLER_ENABLED_KEY = PROPERTY_PREFIX + "journaller.enabled";
35 | private static final String THREAD_AFFINITY_PREFIX = PROPERTY_PREFIX + "affinity.";
36 | private static final String PRODUCER = "producer";
37 | private static final String JOURNALLER = "journaller";
38 | private static final String ACCUMULATOR = "accumulator";
39 | private static final boolean DEFAULT_JOURNALLER_ENABLED_VALUE = false;
40 |
41 | private final String overrideFile;
42 | private final Properties properties = new Properties();
43 |
44 | public Overrides(final CommandLineArgs commandLineArgs)
45 | {
46 | this.overrideFile = commandLineArgs.getOverrideFile();
47 | }
48 |
49 | public void init() throws IOException
50 | {
51 | final File file = new File(overrideFile);
52 | if(!file.exists())
53 | {
54 | System.out.println(format("Cannot find overrides file, creating one for you at %s", overrideFile));
55 | properties.setProperty(threadAffinityKeyFor(PRODUCER), "");
56 | properties.setProperty(threadAffinityKeyFor(JOURNALLER), "");
57 | properties.setProperty(threadAffinityKeyFor(ACCUMULATOR), "");
58 | properties.setProperty(JOURNALLER_ENABLED_KEY, Boolean.toString(DEFAULT_JOURNALLER_ENABLED_VALUE));
59 |
60 | try(final FileWriter writer = new FileWriter(file, false))
61 | {
62 | properties.store(writer, "override properties for perf workshop");
63 | }
64 | }
65 | else
66 | {
67 | System.out.println(format("Loading overrides file from %s", overrideFile));
68 | try (final FileReader reader = new FileReader(file))
69 | {
70 | properties.load(reader);
71 | }
72 |
73 | ensurePropertiesArePresent(JOURNALLER_ENABLED_KEY, threadAffinityKeyFor(PRODUCER),
74 | threadAffinityKeyFor(JOURNALLER), threadAffinityKeyFor(ACCUMULATOR));
75 | }
76 | }
77 |
78 | public int[] getProducerThreadAffinity()
79 | {
80 | return getThreadAffinity(PRODUCER);
81 | }
82 |
83 | public int[] getJournallerThreadAffinity()
84 | {
85 | return getThreadAffinity(JOURNALLER);
86 | }
87 |
88 | public int[] getAccumulatorThreadAffinity()
89 | {
90 | return getThreadAffinity(ACCUMULATOR);
91 | }
92 |
93 | public boolean enableJournaller()
94 | {
95 | return Boolean.valueOf(properties.getProperty(JOURNALLER_ENABLED_KEY));
96 | }
97 |
98 | private void ensurePropertiesArePresent(final String... requiredKeys)
99 | {
100 | stream(requiredKeys).forEach((key) -> {
101 | if(!properties.stringPropertyNames().contains(key))
102 | {
103 | throw new IllegalStateException(
104 | format("Required property %s not found! Try deleting the override file, and it will be regenerated",
105 | key));
106 | }
107 | });
108 | }
109 |
110 | private int[] getThreadAffinity(final String threadName)
111 | {
112 | final String threadAffinity = properties.getProperty(threadAffinityKeyFor(threadName));
113 |
114 | if(threadAffinity != null && !"".equals(threadAffinity))
115 | {
116 | final List cpuSet = stream(threadAffinity.split(",")).
117 | map(Integer::parseInt).collect(toList());
118 |
119 | final int[] cpus = new int[cpuSet.size()];
120 | for(int i = 0; i < cpuSet.size(); i++)
121 | {
122 | cpus[i] = cpuSet.get(i);
123 | }
124 |
125 | return cpus;
126 | }
127 |
128 | return new int[0];
129 | }
130 |
131 | private static String threadAffinityKeyFor(final String threadName)
132 | {
133 | return THREAD_AFFINITY_PREFIX + threadName;
134 | }
135 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/reporting/AccumulatorReporter.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.reporting;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.beust.jcommander.JCommander;
21 | import com.epickrram.workshop.perf.app.processors.Accumulator;
22 | import com.epickrram.workshop.perf.config.CommandLineArgs;
23 | import org.HdrHistogram.Histogram;
24 | import org.HdrHistogram.HistogramLogReader;
25 |
26 | import java.io.File;
27 | import java.io.FileNotFoundException;
28 | import java.io.IOException;
29 | import java.nio.file.Files;
30 | import java.util.List;
31 | import java.util.regex.Pattern;
32 |
33 | import static com.epickrram.workshop.perf.support.Histograms.HISTOGRAMS;
34 | import static java.util.Arrays.stream;
35 | import static java.util.stream.Collectors.toList;
36 |
37 | public final class AccumulatorReporter
38 | {
39 | private static final Pattern NUMBER_PATTERN = Pattern.compile("^[^0-9]+\\-([0-9]+)\\.enc$");
40 |
41 | private final CommandLineArgs commandLineArgs;
42 |
43 | public AccumulatorReporter(final CommandLineArgs commandLineArgs)
44 | {
45 | this.commandLineArgs = commandLineArgs;
46 | }
47 |
48 | public void run() throws IOException
49 | {
50 | reportHistogram("Accumulator Inter-Message Latency (ns)", Accumulator.INTER_MESSAGE_HISTOGRAM_QUALIFIER);
51 | reportHistogram("Accumulator Message Transit Latency (ns)", Accumulator.TRANSIT_TIME_HISTOGRAM_QUALIFIER);
52 | }
53 |
54 | public void cleanUp() throws IOException
55 | {
56 | stream(new File(commandLineArgs.getOutputDir()).listFiles()).
57 | filter((file) -> file.getName().endsWith(".enc")).
58 | filter((file) -> !file.getName().contains("report")).
59 | forEach((file) -> {
60 |
61 | try
62 | {
63 | Files.delete(file.toPath());
64 | }
65 | catch (IOException e)
66 | {
67 | // ignore
68 | }
69 | });
70 | }
71 |
72 | private void reportHistogram(final String histogramTitle, final String histogramQualifier) throws IOException
73 | {
74 | final List encodedHistogramsGeneratedAfterWarmup = stream(new File(commandLineArgs.getOutputDir()).listFiles()).
75 | filter((file) -> file.getName().endsWith(".enc")).
76 | filter((file) -> {
77 | return file.getName().contains(histogramQualifier);
78 | }).
79 | collect(toList());
80 |
81 | final Histogram superHistogram = merge(encodedHistogramsGeneratedAfterWarmup);
82 |
83 | new HistogramReporter(commandLineArgs.getExecutionTimestamp(), commandLineArgs.getOutputDir(),
84 | commandLineArgs.getTestLabel()).writeReport(superHistogram, System.out, commandLineArgs.getReportFormats(), histogramTitle);
85 | }
86 |
87 | private Histogram merge(final List encodedHistogramsGeneratedAfterWarmup)
88 | {
89 | final Histogram histogram = HISTOGRAMS.createHistogram();
90 |
91 | encodedHistogramsGeneratedAfterWarmup.stream().forEach((file) -> {
92 | histogram.add(loadHistogram(file));
93 | });
94 |
95 | return histogram;
96 | }
97 |
98 | private Histogram loadHistogram(final File file)
99 | {
100 | try
101 | {
102 | return (Histogram) new HistogramLogReader(file).nextIntervalHistogram();
103 | }
104 | catch (FileNotFoundException e)
105 | {
106 | throw new RuntimeException("Could not process encoded histogram", e);
107 | }
108 | }
109 |
110 | public static void main(final String[] args) throws Exception
111 | {
112 | final CommandLineArgs commandLineArgs = new CommandLineArgs();
113 | new JCommander(commandLineArgs).parse(args);
114 |
115 | final AccumulatorReporter accumulatorReporter = new AccumulatorReporter(commandLineArgs);
116 | try
117 | {
118 | accumulatorReporter.run();
119 | }
120 | finally
121 | {
122 | accumulatorReporter.cleanUp();
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/reporting/HistogramReporter.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.reporting;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import org.HdrHistogram.Histogram;
21 | import org.HdrHistogram.HistogramLogWriter;
22 |
23 | import java.io.File;
24 | import java.io.FileNotFoundException;
25 | import java.io.IOException;
26 | import java.io.PrintStream;
27 | import java.io.PrintWriter;
28 | import java.time.LocalDateTime;
29 | import java.time.ZoneOffset;
30 | import java.time.format.DateTimeFormatter;
31 | import java.util.Set;
32 |
33 | import static java.lang.String.format;
34 |
35 | public final class HistogramReporter
36 | {
37 | private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
38 | private final long executionTimestamp;
39 | private final String outputDir;
40 | private final String testLabel;
41 |
42 | public HistogramReporter(final long executionTimestamp, final String outputDir, final String testLabel)
43 | {
44 | this.executionTimestamp = executionTimestamp;
45 | this.outputDir = outputDir;
46 | this.testLabel = testLabel;
47 | }
48 |
49 | public void writeReport(final Histogram histogram, final PrintStream out,
50 | final Set reportFormats,
51 | final String histogramTitle) throws IOException
52 | {
53 | if (histogram.getTotalCount() == 0L)
54 | {
55 | return;
56 | }
57 | for (final ReportFormat reportFormat : reportFormats)
58 | {
59 | switch (reportFormat)
60 | {
61 | case LONG:
62 | longReport(histogram, histogramTitle, out);
63 | break;
64 | case SHORT:
65 | shortReport(histogram, out);
66 | break;
67 | case DETAILED:
68 | encodedHistogram(histogram, histogramTitle, out, testLabel);
69 | break;
70 | default:
71 | throw new IllegalStateException("Unknown report format: " + reportFormat);
72 | }
73 | }
74 | }
75 |
76 | private void encodedHistogram(final Histogram histogram, final String histogramTitle, final PrintStream out, final String testLabel)
77 | {
78 | try
79 | {
80 | final File histogramOutputFile = getHistogramOutputFile(outputDir, histogramTitle, testLabel);
81 | out.println("Writing full encoded histogram to " + histogramOutputFile.getAbsolutePath() + "\n");
82 | try (final PrintStream printStream = new PrintStream(histogramOutputFile))
83 | {
84 | new HistogramLogWriter(printStream).outputIntervalHistogram(0, 1, histogram, 1d);
85 | }
86 | }
87 | catch (FileNotFoundException e)
88 | {
89 | throw new RuntimeException("Failed to write histogram", e);
90 | }
91 | }
92 |
93 | private File getHistogramOutputFile(final String rootDir, final String qualifier, final String testLabel)
94 | {
95 | final LocalDateTime timestamp = LocalDateTime.ofEpochSecond(executionTimestamp / 1000, 0, ZoneOffset.UTC);
96 | return new File(rootDir, "encoded-result-histogram-" + cleanse(qualifier) +
97 | "-" + FORMATTER.format(timestamp) + "-LABEL_" + testLabel + ".report.enc");
98 | }
99 |
100 | private String cleanse(final String qualifier)
101 | {
102 | return qualifier.replaceAll("[ =\\(\\)]", "_");
103 | }
104 |
105 | private void longReport(final Histogram histogram, final String histogramTitle,
106 | final PrintStream out) throws IOException
107 | {
108 | final PrintWriter printWriter = new PrintWriter(out);
109 | printWriter.append(format("== %s ==%n", histogramTitle));
110 | printWriter.append(format("%-8s%20d%n", "mean", (long) histogram.getMean()));
111 | printWriter.append(format("%-8s%20d%n", "min", histogram.getMinValue()));
112 | printWriter.append(format("%-8s%20d%n", "50.00%", histogram.getValueAtPercentile(50.0d)));
113 | printWriter.append(format("%-8s%20d%n", "90.00%", histogram.getValueAtPercentile(90.0d)));
114 | printWriter.append(format("%-8s%20d%n", "99.00%", histogram.getValueAtPercentile(99.0d)));
115 | printWriter.append(format("%-8s%20d%n", "99.90%", histogram.getValueAtPercentile(99.9d)));
116 | printWriter.append(format("%-8s%20d%n", "99.99%", histogram.getValueAtPercentile(99.99d)));
117 | printWriter.append(format("%-8s%20d%n", "99.999%", histogram.getValueAtPercentile(99.999d)));
118 | printWriter.append(format("%-8s%20d%n", "99.9999%", histogram.getValueAtPercentile(99.9999d)));
119 | printWriter.append(format("%-8s%20d%n", "max", histogram.getMaxValue()));
120 | printWriter.append(format("%-8s%20d%n", "count", histogram.getTotalCount()));
121 | printWriter.append("\n");
122 | printWriter.flush();
123 | }
124 |
125 | private void shortReport(final Histogram histogram, final PrintStream out) throws IOException
126 | {
127 | final PrintWriter printWriter = new PrintWriter(out);
128 | printWriter.append(format("%d,", histogram.getMinValue()));
129 | printWriter.append(format("%d,", (long) histogram.getMean()));
130 | printWriter.append(format("%d,", histogram.getValueAtPercentile(50.0d)));
131 | printWriter.append(format("%d,", histogram.getValueAtPercentile(90.0d)));
132 | printWriter.append(format("%d,", histogram.getValueAtPercentile(99.0d)));
133 | printWriter.append(format("%d,", histogram.getValueAtPercentile(99.9d)));
134 | printWriter.append(format("%d,", histogram.getValueAtPercentile(99.99d)));
135 | printWriter.append(format("%d,", histogram.getMaxValue()));
136 | printWriter.append(format("%d%n", histogram.getTotalCount()));
137 | printWriter.append("\n");
138 | printWriter.flush();
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/reporting/JournalReporter.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.reporting;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.beust.jcommander.JCommander;
21 | import com.epickrram.workshop.perf.app.message.JournalEntry;
22 | import com.epickrram.workshop.perf.config.CommandLineArgs;
23 | import org.HdrHistogram.Histogram;
24 |
25 | import java.io.File;
26 | import java.io.IOException;
27 | import java.nio.channels.FileChannel;
28 |
29 | import static com.epickrram.workshop.perf.support.Histograms.HISTOGRAMS;
30 | import static java.nio.channels.FileChannel.open;
31 | import static java.nio.file.StandardOpenOption.READ;
32 |
33 | public final class JournalReporter
34 | {
35 | private final CommandLineArgs commandLineArgs;
36 |
37 | public JournalReporter(final CommandLineArgs commandLineArgs)
38 | {
39 | this.commandLineArgs = commandLineArgs;
40 | }
41 |
42 | public void run() throws IOException
43 | {
44 | final FileChannel channel = open(new File(commandLineArgs.getJournalFile()).toPath(), READ);
45 | final Histogram journallerInterMessageLatency = HISTOGRAMS.createHistogram();
46 | final Histogram messageTransitLatency = HISTOGRAMS.createHistogram();
47 | final Histogram publisherInterMessageLatency = HISTOGRAMS.createHistogram();
48 | final int numberOfEntriesToIgnoreDueToWarmup = commandLineArgs.getNumberOfWarmups() * commandLineArgs.getNumberOfRecords();
49 |
50 | final JournalEntry journalEntry = new JournalEntry();
51 | long previousMessageJournallerNanos = 0L;
52 | long previousMessagePublisherNanos = 0L;
53 | int messageCount = 0;
54 |
55 | while(channel.position() < channel.size() - JournalEntry.ENTRY_SIZE)
56 | {
57 | journalEntry.readFrom(channel);
58 |
59 | if(!journalEntry.canRead())
60 | {
61 | break;
62 | }
63 |
64 | if(++messageCount > numberOfEntriesToIgnoreDueToWarmup)
65 | {
66 | HISTOGRAMS.safeRecord(journalEntry.getDeltaNanos(), messageTransitLatency);
67 | if (previousMessageJournallerNanos != 0L && journalEntry.getSequenceInFile() != 0)
68 | {
69 | HISTOGRAMS.safeRecord(journalEntry.getJournallerNanoTime() - previousMessageJournallerNanos, journallerInterMessageLatency);
70 | HISTOGRAMS.safeRecord(journalEntry.getPublisherNanoTime() - previousMessagePublisherNanos, publisherInterMessageLatency);
71 | }
72 |
73 | previousMessageJournallerNanos = journalEntry.getJournallerNanoTime();
74 | previousMessagePublisherNanos = journalEntry.getPublisherNanoTime();
75 | }
76 | }
77 |
78 | final HistogramReporter histogramReporter =
79 | new HistogramReporter(commandLineArgs.getExecutionTimestamp(), commandLineArgs.getOutputDir(), commandLineArgs.getTestLabel());
80 | histogramReporter.writeReport(publisherInterMessageLatency, System.out,
81 | commandLineArgs.getReportFormats(),
82 | "Publisher Inter-Message Latency (ns)");
83 | histogramReporter.writeReport(journallerInterMessageLatency, System.out,
84 | commandLineArgs.getReportFormats(),
85 | "Journaller Inter-Message Latency (ns)");
86 | histogramReporter.writeReport(messageTransitLatency, System.out,
87 | commandLineArgs.getReportFormats(),
88 | "Journaller Message Transit Latency (ns)");
89 | }
90 |
91 | public static void main(final String[] args) throws Exception
92 | {
93 | final CommandLineArgs commandLineArgs = new CommandLineArgs();
94 | new JCommander(commandLineArgs).parse(args);
95 |
96 | new JournalReporter(commandLineArgs).run();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/reporting/PostProcess.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.reporting;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | public final class PostProcess
21 | {
22 | public static void main(final String[] args) throws Exception
23 | {
24 | JournalReporter.main(args);
25 | AccumulatorReporter.main(args);
26 |
27 | System.out.flush();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/reporting/ReportFormat.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.reporting;
2 |
3 | public enum ReportFormat
4 | {
5 | LONG,
6 | SHORT,
7 | DETAILED
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/setup/InputGenerator.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.setup;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import com.beust.jcommander.JCommander;
21 | import com.epickrram.workshop.perf.config.CommandLineArgs;
22 |
23 | import java.io.File;
24 | import java.io.FileOutputStream;
25 | import java.io.IOException;
26 | import java.io.OutputStream;
27 | import java.util.Random;
28 |
29 | public final class InputGenerator
30 | {
31 | private final CommandLineArgs commandLineArgs;
32 |
33 | public InputGenerator(final CommandLineArgs commandLineArgs)
34 | {
35 | this.commandLineArgs = commandLineArgs;
36 | }
37 |
38 | public static void main(final String[] args) throws Exception
39 | {
40 | final CommandLineArgs commandLineArgs = new CommandLineArgs();
41 | new JCommander(commandLineArgs).parse(args);
42 |
43 | new InputGenerator(commandLineArgs).run();
44 |
45 | System.out.printf("Created input journal of %dkb%n", (commandLineArgs.getNumberOfRecords() * commandLineArgs.getRecordLength() / 1024));
46 | }
47 |
48 | private void run() throws IOException
49 | {
50 | final Random random = new Random(0xDEADC0DE);
51 | final byte[] record = new byte[commandLineArgs.getRecordLength()];
52 | try(final OutputStream stream = new FileOutputStream(new File(commandLineArgs.getInputFile())))
53 | {
54 | for(int recordNumber = 0; recordNumber < commandLineArgs.getNumberOfRecords(); recordNumber++)
55 | {
56 | random.nextBytes(record);
57 | stream.write(record);
58 | }
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/support/DaemonThreadFactory.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.support;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import java.util.concurrent.ThreadFactory;
21 |
22 | public enum DaemonThreadFactory implements ThreadFactory
23 | {
24 | DAEMON_THREAD_FACTORY;
25 |
26 | @Override
27 | public Thread newThread(final Runnable r)
28 | {
29 | final Thread thread = new Thread(r);
30 | thread.setDaemon(true);
31 | return thread;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/support/EncodedHistogramReporter.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.support;
2 |
3 | import com.epickrram.workshop.perf.reporting.HistogramReporter;
4 | import com.epickrram.workshop.perf.reporting.ReportFormat;
5 | import org.HdrHistogram.Histogram;
6 | import org.HdrHistogram.HistogramLogReader;
7 |
8 | import java.io.IOException;
9 | import java.util.EnumSet;
10 |
11 | public final class EncodedHistogramReporter
12 | {
13 | public static void main(final String[] args) throws IOException
14 | {
15 | final Histogram histogram = (Histogram) new HistogramLogReader(args[0]).nextIntervalHistogram();
16 | final HistogramReporter reporter = new HistogramReporter(0L, null, "");
17 | reporter.writeReport(histogram, System.out, EnumSet.of(ReportFormat.LONG), args[0]);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/support/Histograms.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.support;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import org.HdrHistogram.Histogram;
21 |
22 | import java.util.concurrent.TimeUnit;
23 |
24 | import static java.lang.Math.min;
25 |
26 | public enum Histograms
27 | {
28 | HISTOGRAMS;
29 |
30 | public Histogram createHistogramForArray(final int index)
31 | {
32 | return new Histogram(TimeUnit.SECONDS.toNanos(1L), 1);
33 | }
34 |
35 | public Histogram createHistogram()
36 | {
37 | return new Histogram(TimeUnit.SECONDS.toNanos(1L), 5);
38 | }
39 |
40 | public void safeRecord(final long value, final Histogram histogram)
41 | {
42 | histogram.recordValue(min(histogram.getHighestTrackableValue(), value));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/support/NanoTimer.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.support;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | public interface NanoTimer
21 | {
22 | long nanoTime();
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/support/SpinLoopHintBusySpinWaitStrategy.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.support;
2 |
3 | import com.lmax.disruptor.AlertException;
4 | import com.lmax.disruptor.Sequence;
5 | import com.lmax.disruptor.SequenceBarrier;
6 | import com.lmax.disruptor.WaitStrategy;
7 | import org.performancehints.SpinHint;
8 |
9 | public final class SpinLoopHintBusySpinWaitStrategy implements WaitStrategy
10 | {
11 | @Override
12 | public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier)
13 | throws AlertException, InterruptedException
14 | {
15 | long availableSequence;
16 |
17 | while ((availableSequence = dependentSequence.get()) < sequence)
18 | {
19 | SpinHint.spinLoopHint();
20 | barrier.checkAlert();
21 | }
22 |
23 | return availableSequence;
24 | }
25 |
26 | @Override
27 | public void signalAllWhenBlocking()
28 | {
29 | }
30 | }
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/support/SystemNanoTimer.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.support;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | public enum SystemNanoTimer implements NanoTimer
21 | {
22 | SYSTEM_NANO_TIMER;
23 |
24 | @Override
25 | public long nanoTime()
26 | {
27 | return System.nanoTime();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/epickrram/workshop/perf/support/Threads.java:
--------------------------------------------------------------------------------
1 | package com.epickrram.workshop.perf.support;
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Copyright 2015 Mark Price mark at epickrram.com //
5 | // //
6 | // Licensed under the Apache License, Version 2.0 (the "License"); //
7 | // you may not use this file except in compliance with the License. //
8 | // You may obtain a copy of the License at //
9 | // //
10 | // http://www.apache.org/licenses/LICENSE-2.0 //
11 | // //
12 | // Unless required by applicable law or agreed to in writing, software //
13 | // distributed under the License is distributed on an "AS IS" BASIS, //
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
15 | // See the License for the specific language governing permissions and //
16 | // limitations under the License. //
17 | //////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | import java.io.IOException;
21 | import java.nio.file.Files;
22 | import java.nio.file.Paths;
23 | import java.util.Arrays;
24 | import java.util.BitSet;
25 | import java.util.List;
26 | import java.util.concurrent.TimeUnit;
27 |
28 | public enum Threads
29 | {
30 | THREADS;
31 |
32 | public Runnable runOnCpu(final Runnable task, final int... cpus)
33 | {
34 | return () -> {
35 | setCurrentThreadAffinity(cpus);
36 | task.run();
37 | };
38 | }
39 |
40 | public void setCurrentThreadAffinity(final int... cpus)
41 | {
42 | if (cpus.length == 0)
43 | {
44 | return;
45 | }
46 | if (cpus.length != 1)
47 | {
48 | throw new UnsupportedOperationException();
49 | }
50 |
51 | ProcessBuilder builder = new ProcessBuilder("taskset", "-cp",
52 | Integer.toString(cpus[0]), Integer.toString(getCurrentThreadId()));
53 |
54 | try
55 | {
56 | builder.start().waitFor();
57 | System.out.println("Set affinity for thread " + Thread.currentThread().getName() + " to " + Arrays.toString(cpus));
58 | }
59 | catch (InterruptedException | IOException e)
60 | {
61 | e.printStackTrace();
62 | }
63 | }
64 |
65 | public int getCurrentThreadId()
66 | {
67 | try
68 | {
69 | final List strings = Files.readAllLines(Paths.get("/proc/thread-self/status"));
70 | return Integer.parseInt(strings.stream().filter(s -> s.startsWith("Pid:")).map(s -> {
71 | return s.split("\\s+")[1];
72 | }).findAny().get());
73 |
74 | //
75 | // // TODO: if Java 9, then use ProcessHandle.
76 | // final String pidPorpertyValue = System.getProperty("sun.java.launcher.pid");
77 | //
78 | // if (null != pidPorpertyValue)
79 | // {
80 | // return Integer.parseInt(pidPorpertyValue);
81 | // }
82 | //
83 | // final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
84 | //
85 | // return Integer.parseInt(jvmName.split("@")[0]);
86 | }
87 | catch (final Throwable ex)
88 | {
89 | return -1;
90 | }
91 | }
92 |
93 | public void sleep(final long duration, final TimeUnit unit)
94 | {
95 | try
96 | {
97 | Thread.sleep(unit.toMillis(duration));
98 | }
99 | catch (InterruptedException e)
100 | {
101 | throw new RuntimeException("Interrupted during sleep!");
102 | }
103 | }
104 |
105 | private BitSet cpuListToBitMask(final int[] cpus)
106 | {
107 | final BitSet bitSet = new BitSet();
108 |
109 | for(int cpu : cpus)
110 | {
111 | bitSet.set(cpu);
112 | }
113 |
114 | return bitSet;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/org/performancehints/SpinHint.java:
--------------------------------------------------------------------------------
1 | package org.performancehints;
2 |
3 | /**
4 | * COPIED FROM
5 | * https://github.com/giltene/GilExamples/blob/master/SpinHintTest/src/main/java/org/performancehints/SpinHint.java
6 | */
7 | public final class SpinHint
8 | {
9 | // sole ctor
10 | private SpinHint() {}
11 |
12 | /**
13 | * Provides a hint to the processor that the code sequence is a spin-wait loop.
14 | */
15 | public static void spinLoopHint()
16 | {
17 | // intentionally empty
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/python/perf_sched_stat_runtime.py:
--------------------------------------------------------------------------------
1 | __author__ = 'pricem'
2 |
3 | import re
4 | import sys
5 |
6 | TIMESTAMP_REGEX=".* ([0-9]{1,12}\.[0-9]{6}):.*"
7 | RUNTIME_REGEX=".* runtime=([0-9]+) "
8 |
9 | def parse_timestamp(input):
10 | return int(float(input) * 1000000) * 1000
11 |
12 | def parse_runtime(input):
13 | return int(input)
14 |
15 | if len(sys.argv) == 1:
16 | print "usage: " + sys.argv[0] + " "
17 |
18 | last_timestamp = 0
19 |
20 | for line in open(sys.argv[2]).readlines():
21 | if line.find("sched_stat_runtime") > 0 and line.find("pid=" + sys.argv[1]) > 0:
22 | timestamp_match = re.search(TIMESTAMP_REGEX, line)
23 | runtime_match = re.search(RUNTIME_REGEX, line)
24 |
25 | timestamp_nanos = parse_timestamp(timestamp_match.group(1))
26 | runtime_nanos = parse_runtime(runtime_match.group(1))
27 |
28 | if last_timestamp != 0:
29 | runtime_reporting_delta_nanos = timestamp_nanos - last_timestamp
30 | nanos_not_on_cpu = abs(runtime_reporting_delta_nanos - runtime_nanos)
31 | print str(timestamp_nanos) + " " + str(nanos_not_on_cpu)
32 |
33 | last_timestamp = timestamp_nanos
--------------------------------------------------------------------------------
/src/main/python/perf_softirq_duration.py:
--------------------------------------------------------------------------------
1 | __author__ = 'pricem'
2 |
3 | import re
4 | import sys
5 |
6 | TIMESTAMP_REGEX=".* ([0-9]{1,12}\.[0-9]{6}):.*"
7 | CAUSE_REGEX=".*action=([^\]]+)"
8 |
9 |
10 | def parse_timestamp(input):
11 | return int(float(input) * 1000000) * 1000
12 |
13 | def parse_runtime(input):
14 | return int(input)
15 |
16 | if len(sys.argv) == 1:
17 | print "usage: " + sys.argv[0] + " "
18 |
19 | last_timestamp = 0
20 | start_of_irq_entry_by_pid_and_cpu = {}
21 |
22 | def get_key_from_tokens(line):
23 | tokens = re.split("\s+", line.strip())
24 | key = tokens[1] + "-" + tokens[2]
25 | return key
26 |
27 | for line in open(sys.argv[1]).readlines():
28 | if line.find("irq:softirq_entry") > 0:
29 | timestamp_match = re.search(TIMESTAMP_REGEX, line)
30 | cause_match = re.search(CAUSE_REGEX, line)
31 |
32 | start_of_work = parse_timestamp(timestamp_match.group(1))
33 | cause = cause_match.group(1)
34 |
35 | key = get_key_from_tokens(line)
36 |
37 | if key in start_of_irq_entry_by_pid_and_cpu:
38 | print "Did not consume previous entry for: " + line
39 |
40 | start_of_irq_entry_by_pid_and_cpu[key] = [start_of_work, cause]
41 |
42 |
43 | elif line.find("irq:softirq_exit") > 0:
44 | timestamp_match = re.search(TIMESTAMP_REGEX, line)
45 | end_of_work = parse_timestamp(timestamp_match.group(1))
46 | key = get_key_from_tokens(line)
47 | if key not in start_of_irq_entry_by_pid_and_cpu:
48 | print "Did not record start time for: " + line
49 | previous_entry = start_of_irq_entry_by_pid_and_cpu[key]
50 | del start_of_irq_entry_by_pid_and_cpu[key]
51 |
52 | duration_of_work = end_of_work - previous_entry[0]
53 | print str(int(duration_of_work / 1000)) + "us " + previous_entry[1] + " for pid/cpu: " + key
--------------------------------------------------------------------------------
/src/main/python/perf_workqueue_duration.py:
--------------------------------------------------------------------------------
1 | __author__ = 'pricem'
2 |
3 | import re
4 | import sys
5 |
6 | TIMESTAMP_REGEX=".* ([0-9]{1,12}\.[0-9]{6}):.*"
7 | FUNCTION_REGEX=".* function ([^\s]+)"
8 |
9 |
10 | def parse_timestamp(input):
11 | return int(float(input) * 1000000) * 1000
12 |
13 | def parse_runtime(input):
14 | return int(input)
15 |
16 | if len(sys.argv) == 1:
17 | print "usage: " + sys.argv[0] + " "
18 |
19 | last_timestamp = 0
20 | start_of_work = 0
21 |
22 | for line in open(sys.argv[1]).readlines():
23 | if line.find("workqueue_execute_start") > 0:
24 | timestamp_match = re.search(TIMESTAMP_REGEX, line)
25 | function_match = re.search(FUNCTION_REGEX, line)
26 |
27 | start_of_work = parse_timestamp(timestamp_match.group(1))
28 | function = function_match.group(1)
29 | elif line.find("workqueue_execute_end") > 0:
30 | timestamp_match = re.search(TIMESTAMP_REGEX, line)
31 | end_of_work = parse_timestamp(timestamp_match.group(1))
32 | duration_of_work = end_of_work - start_of_work
33 | print str(duration_of_work) + " " + function
--------------------------------------------------------------------------------
/src/main/shell/chart_accumulator_message_transit_latency.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | HISTOGRAM_SOURCE_DIR="/tmp"
4 |
5 | if [ "$1" != "" ]; then
6 | HISTOGRAM_SOURCE_DIR=$1
7 | fi
8 |
9 | echo "Looking for encoded histogram files in $HISTOGRAM_SOURCE_DIR"
10 |
11 | CHART_FILE="accumulator_message_transit_latency.cmd"
12 | bash ./convert_encoded_histogram.sh "$HISTOGRAM_SOURCE_DIR"
13 |
14 | FILES=`ls $HISTOGRAM_SOURCE_DIR/encoded-result-histogram-Accumulator_Message_Transit_Latency__ns_*.report.enc.decoded.hgrm`
15 |
16 | echo "set logscale x" > $CHART_FILE
17 | echo "unset xtics" >> $CHART_FILE
18 | echo "set xlabel \"Percentile\"" >> $CHART_FILE
19 | echo "set ylabel \"Latency nanoseconds\"" >> $CHART_FILE
20 | echo "set title \"Inter-thread latency breakdown\"" >> $CHART_FILE
21 | echo "plot \\" >> $CHART_FILE
22 |
23 | for f in $FILES; do
24 | LABEL=`echo $f | grep -oE "LABEL_([^\.]+)" | sed -e 's/LABEL_//g'`
25 | echo "'$f' using 4:1 with lines title \"$LABEL\", \\" >> $CHART_FILE
26 | done
27 |
28 | echo "'./xlabels.dat' with labels center offset 0, 1.5 point title \"\"" >> $CHART_FILE
29 | echo "pause -1" >> $CHART_FILE
30 |
31 |
--------------------------------------------------------------------------------
/src/main/shell/convert_encoded_histogram.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | HISTOGRAM_SOURCE_DIR="/tmp"
4 |
5 | if [ "$1" != "" ]; then
6 | HISTOGRAM_SOURCE_DIR=$1
7 | fi
8 |
9 | for i in `ls $HISTOGRAM_SOURCE_DIR/*.report.enc`; do ../../../deps/jHiccup-2.0.5/jHiccupLogProcessor -i $i -o $i.decoded -outputValueUnitRatio 1; done
10 |
11 |
--------------------------------------------------------------------------------
/src/main/shell/get_pids.sh:
--------------------------------------------------------------------------------
1 | jstack "$1" | grep -E "(Producer|Accumulator)"
2 |
--------------------------------------------------------------------------------
/src/main/shell/run_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ "$JAVA_HOME" == "" ]; then
4 | echo "JAVA_HOME is not set!"
5 | exit 1
6 | fi
7 |
8 | JAVA="$JAVA_HOME/bin/java"
9 | LIB_LOCATION="../../../build/libs/perf-workshop-all-0.0.1.jar"
10 |
11 | echo "Executing test with:"
12 | $JAVA -version
13 | DEFAULT_JVM_OPTS="-XX:+UnlockDiagnosticVMOptions -XX:GuaranteedSafepointInterval=600000"
14 | JVM_OPTS="$DEFAULT_JVM_OPTS -XX:-TieredCompilation -XX:+DisableExplicitGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime -XX:+PrintTenuringDistribution -XX:-UseBiasedLocking"
15 | EXEC_PREFIX=""
16 |
17 | echo "JVM_OPTS: $JVM_OPTS"
18 |
19 | TEST_LABEL_ARG=""
20 | if [ "$1" != "" ]; then
21 | TEST_LABEL_ARG="-t $1"
22 | echo "Using test label $1"
23 | fi
24 |
25 | $EXEC_PREFIX $JAVA -Xmx4g -Xms4g $JVM_OPTS -Xloggc:perf-workshop-gc.log -jar $LIB_LOCATION -i 2500 -w 300 -n 4000 -r DETAILED -r LONG $TEST_LABEL_ARG | tee output.log
26 |
--------------------------------------------------------------------------------
/src/main/shell/set_cpu_governor.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ "$1" == "" ]; then
4 | echo "Supply governor [powersave|performance] as first argument!"
5 | exit
6 | fi
7 |
8 |
9 | for i in `ls /sys/devices/system/cpu/ | grep "cpu[0-9]" | grep -v idle`; do echo $1 > /sys/devices/system/cpu/$i/cpufreq/scaling_governor ;done
10 |
--------------------------------------------------------------------------------
/src/main/shell/set_irq_affinity.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ "$1" == "" ]; then
4 | echo "Supply cpu list as first argument!"
5 | exit
6 | fi
7 |
8 | for i in `ls /proc/irq/ | grep -v default`; do echo "$1" > /proc/irq/$i/smp_affinity_list ; done
9 |
--------------------------------------------------------------------------------
/src/main/shell/set_rcu_thread_affinity.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ "$1" == "" ]; then
4 | echo "Supply cpu list as first argument!"
5 | exit
6 | fi
7 |
8 | for i in `ps aux | grep rcuo | grep -v grep | awk '{print $2}'`; do taskset -cp $1 $i; done
--------------------------------------------------------------------------------
/src/main/shell/set_thread_affinity.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import os
4 | import subprocess
5 | import sys
6 | import time
7 |
8 | counts=dict()
9 |
10 | if os.environ["JAVA_HOME"] is None:
11 | raise RuntimeError
12 |
13 | result = subprocess.check_call([os.environ["JAVA_HOME"] + "/bin/jps"], stdout=open('/tmp/jps-out', 'w+'))
14 | with open('/tmp/jps-out') as f:
15 | content = f.readlines()
16 |
17 | for line in content:
18 | if line.find("Jps") < 0 and len(line.strip()) != 0 and line.find("perf-workshop-all-0.0.1.jar") != -1:
19 | pid = line.split(" ")[0].strip()
20 | pids = subprocess.check_call(([os.environ["JAVA_HOME"] + "/bin/jstack", str(pid)]), stdout=open('/tmp/jstack-out', 'w+'))
21 | with open('/tmp/jstack-out') as g:
22 | stackcontent = g.readlines()
23 | print "Application pid: " + str(pid)
24 | cpu_index = 1
25 | for thread in stackcontent:
26 | if thread.find("nid=0x") > -1:
27 | tokens = thread.strip().split(" ")
28 | for token in tokens:
29 | if token.find("nid=0x") != -1:
30 | tid = token.split("=")
31 | if tokens[0].find("Producer") != -1 or tokens[0].find("Accumulator") != -1:
32 | # do taskset
33 | cpu = sys.argv[cpu_index]
34 | affined_tid = str(int(str(tid[1]), 16))
35 | subprocess.check_call(["taskset", "-cp", cpu, affined_tid])
36 | cpu_index += 1
37 | print tokens[0] + " has pid " + affined_tid
38 | print "Set " + tokens[0] + "(" + affined_tid + ") to run on CPU" + cpu
39 | elif tokens[0].find("Journaller") != -1:
40 | print "\"Journaller\" has pid " + affined_tid
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/shell/set_watchdog_thread_affinity.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ "$1" == "" ]; then
4 | echo "Supply cpu list as first argument!"
5 | exit
6 | fi
7 |
8 | for i in `ps aux | grep watchdog | grep -v grep | awk '{print $2}'`; do taskset -cp $1 $i; done
--------------------------------------------------------------------------------
/src/main/shell/terminal_latency.cmd:
--------------------------------------------------------------------------------
1 | # gnuplot -e "cols=${COLUMNS}; rows=${LINES}"
2 | unset output
3 | set terminal dumb
4 | #set output "/tmp/output.ascii"
5 | set term dumb size cols, rows
6 | set origin 0,0
7 | set multiplot
8 | set size 1,0.4
9 | set origin 0,0.6
10 |
11 |
12 | set logscale x
13 | unset xtics
14 | set xlabel "Percentile"
15 | set ylabel "Latency nanoseconds"
16 | set title "Inter-thread latency breakdown"
17 | plot \
18 | '/tmp/encoded-result-histogram-Accumulator_Message_Transit_Latency__ns_-20180327-121054-LABEL_LAPTOP.report.enc.decoded.hgrm' using 4:1 with lines title "LAPTOP", \
19 | './xlabels.dat' with labels center offset 0, 1.5 point title ""
20 |
21 |
22 | unset multiplot
23 | unset output
24 | reset
25 |
--------------------------------------------------------------------------------
/src/main/shell/xlabels.dat:
--------------------------------------------------------------------------------
1 | 1.0 0.0 0%
2 | 10.0 0.0 90%
3 | 100.0 0.0 99%
4 | 1000.0 0.0 99.9%
5 | 10000.0 0.0 99.99%
6 | 100000.0 0.0 99.999%
7 | 1000000.0 0.0 99.9999%
8 | 10000000.0 0.0 99.99999%
9 |
--------------------------------------------------------------------------------