50 final static String PID = getPid(); |
51 final static String PID = getPid(); |
51 |
52 |
52 // looking for header lines with these patterns: |
53 // looking for header lines with these patterns: |
53 // "ContendingThread-1" #19 prio=5 os_prio=64 tid=0x000000000079c000 nid=0x23 runnable [0xffff80ffb8b87000] |
54 // "ContendingThread-1" #19 prio=5 os_prio=64 tid=0x000000000079c000 nid=0x23 runnable [0xffff80ffb8b87000] |
54 // "ContendingThread-2" #21 prio=5 os_prio=64 tid=0x0000000000780000 nid=0x2f waiting for monitor entry [0xfffffd7fc1111000] |
55 // "ContendingThread-2" #21 prio=5 os_prio=64 tid=0x0000000000780000 nid=0x2f waiting for monitor entry [0xfffffd7fc1111000] |
|
56 // "ContendingThread-2" #24 prio=5 os_prio=64 tid=0x0000000000ec8800 nid=0x31 waiting on condition [0xfffffd7bbfffe000] |
55 final static Pattern HEADER_PREFIX_PATTERN = Pattern.compile( |
57 final static Pattern HEADER_PREFIX_PATTERN = Pattern.compile( |
56 "^\"ContendingThread-.*"); |
58 "^\"ContendingThread-.*"); |
57 final static Pattern HEADER_WAITING_PATTERN = Pattern.compile( |
59 final static Pattern HEADER_WAITING_PATTERN1 = Pattern.compile( |
58 "^\"ContendingThread-.* waiting for monitor entry .*"); |
60 "^\"ContendingThread-.* waiting for monitor entry .*"); |
|
61 final static Pattern HEADER_WAITING_PATTERN2 = Pattern.compile( |
|
62 "^\"ContendingThread-.* waiting on condition .*"); |
59 final static Pattern HEADER_RUNNABLE_PATTERN = Pattern.compile( |
63 final static Pattern HEADER_RUNNABLE_PATTERN = Pattern.compile( |
60 "^\"ContendingThread-.* runnable .*"); |
64 "^\"ContendingThread-.* runnable .*"); |
61 |
65 |
62 // looking for thread state lines with these patterns: |
66 // looking for thread state lines with these patterns: |
63 // java.lang.Thread.State: RUNNABLE |
67 // java.lang.Thread.State: RUNNABLE |
78 // with this pattern: |
82 // with this pattern: |
79 // - waiting to lock <0x000000076ac59e20> (a TestThreadDumpMonitorContention$1) |
83 // - waiting to lock <0x000000076ac59e20> (a TestThreadDumpMonitorContention$1) |
80 final static Pattern WAITING_PATTERN = Pattern.compile( |
84 final static Pattern WAITING_PATTERN = Pattern.compile( |
81 ".* waiting to lock \\<.*\\(a TestThreadDumpMonitorContention.*"); |
85 ".* waiting to lock \\<.*\\(a TestThreadDumpMonitorContention.*"); |
82 |
86 |
|
87 final static Object barrier = new Object(); |
83 volatile static boolean done = false; |
88 volatile static boolean done = false; |
84 |
89 |
|
90 static int barrier_cnt = 0; |
|
91 static int blank_line_match_cnt = 0; |
85 static int error_cnt = 0; |
92 static int error_cnt = 0; |
86 static String header_line = null; |
|
87 static boolean have_header_line = false; |
93 static boolean have_header_line = false; |
88 static boolean have_thread_state_line = false; |
94 static boolean have_thread_state_line = false; |
89 static int match_cnt = 0; |
95 static String header_line = null; |
90 static String[] match_list = new String[2]; |
96 static int header_prefix_match_cnt = 0; |
|
97 static int locked_line_match_cnt = 0; |
|
98 static String[] locked_match_list = new String[2]; |
91 static int n_samples = 15; |
99 static int n_samples = 15; |
|
100 static int sum_both_running_cnt = 0; |
|
101 static int sum_both_waiting_cnt = 0; |
|
102 static int sum_contended_cnt = 0; |
|
103 static int sum_locked_hdr_runnable_cnt = 0; |
|
104 static int sum_locked_hdr_waiting1_cnt = 0; |
|
105 static int sum_locked_hdr_waiting2_cnt = 0; |
|
106 static int sum_locked_thr_state_blocked_cnt = 0; |
|
107 static int sum_locked_thr_state_runnable_cnt = 0; |
|
108 static int sum_one_waiting_cnt = 0; |
|
109 static int sum_uncontended_cnt = 0; |
|
110 static int sum_waiting_hdr_waiting1_cnt = 0; |
|
111 static int sum_waiting_thr_state_blocked_cnt = 0; |
92 static String thread_state_line = null; |
112 static String thread_state_line = null; |
93 static boolean verbose = false; |
113 static boolean verbose = false; |
|
114 static int waiting_line_match_cnt = 0; |
94 |
115 |
95 public static void main(String[] args) throws Exception { |
116 public static void main(String[] args) throws Exception { |
96 if (args.length != 0) { |
117 if (args.length != 0) { |
97 int arg_i = 0; |
118 int arg_i = 0; |
98 if (args[arg_i].equals("-v")) { |
119 if (args[arg_i].equals("-v")) { |
108 } |
129 } |
109 } |
130 } |
110 |
131 |
111 Runnable runnable = new Runnable() { |
132 Runnable runnable = new Runnable() { |
112 public void run() { |
133 public void run() { |
|
134 synchronized (barrier) { |
|
135 // let the main thread know we're running |
|
136 barrier_cnt++; |
|
137 barrier.notify(); |
|
138 } |
113 while (!done) { |
139 while (!done) { |
114 synchronized (this) { } |
140 synchronized (this) { } |
115 } |
141 } |
116 } |
142 } |
117 }; |
143 }; |
118 Thread[] thread_list = new Thread[2]; |
144 Thread[] thread_list = new Thread[2]; |
119 thread_list[0] = new Thread(runnable, "ContendingThread-1"); |
145 thread_list[0] = new Thread(runnable, "ContendingThread-1"); |
120 thread_list[1] = new Thread(runnable, "ContendingThread-2"); |
146 thread_list[1] = new Thread(runnable, "ContendingThread-2"); |
121 thread_list[0].start(); |
147 synchronized (barrier) { |
122 thread_list[1].start(); |
148 thread_list[0].start(); |
|
149 thread_list[1].start(); |
|
150 |
|
151 // Wait until the contending threads are running so that |
|
152 // we don't sample any thread init states. |
|
153 while (barrier_cnt < 2) { |
|
154 barrier.wait(); |
|
155 } |
|
156 } |
123 |
157 |
124 doSamples(); |
158 doSamples(); |
125 |
159 |
126 done = true; |
160 done = true; |
127 |
161 |
141 // or WAITING_PATTERN. Rare, but it's not an error. |
175 // or WAITING_PATTERN. Rare, but it's not an error. |
142 // |
176 // |
143 // Example: |
177 // Example: |
144 // "ContendingThread-1" #21 prio=5 os_prio=64 tid=0x00000000007b9000 nid=0x2f runnable [0xfffffd7fc1111000] |
178 // "ContendingThread-1" #21 prio=5 os_prio=64 tid=0x00000000007b9000 nid=0x2f runnable [0xfffffd7fc1111000] |
145 // java.lang.Thread.State: RUNNABLE |
179 // java.lang.Thread.State: RUNNABLE |
146 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:67) |
180 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:140) |
147 // at java.lang.Thread.run(Thread.java:745) |
181 // at java.lang.Thread.run(Thread.java:745) |
148 // |
182 // |
149 static boolean checkBlankLine(String line) { |
183 static boolean checkBlankLine(String line) { |
150 if (line.length() == 0) { |
184 if (line.length() == 0) { |
|
185 blank_line_match_cnt++; |
151 have_header_line = false; |
186 have_header_line = false; |
152 have_thread_state_line = false; |
187 have_thread_state_line = false; |
153 return true; |
188 return true; |
154 } |
189 } |
155 |
190 |
159 // Process the locked line here if we found one. |
194 // Process the locked line here if we found one. |
160 // |
195 // |
161 // Example 1: |
196 // Example 1: |
162 // "ContendingThread-1" #21 prio=5 os_prio=64 tid=0x00000000007b9000 nid=0x2f runnable [0xfffffd7fc1111000] |
197 // "ContendingThread-1" #21 prio=5 os_prio=64 tid=0x00000000007b9000 nid=0x2f runnable [0xfffffd7fc1111000] |
163 // java.lang.Thread.State: RUNNABLE |
198 // java.lang.Thread.State: RUNNABLE |
164 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:67) |
199 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:140) |
165 // - locked <0xfffffd7e6a2912f8> (a TestThreadDumpMonitorContention$1) |
200 // - locked <0xfffffd7e6a2912f8> (a TestThreadDumpMonitorContention$1) |
166 // at java.lang.Thread.run(Thread.java:745) |
201 // at java.lang.Thread.run(Thread.java:745) |
167 // |
202 // |
168 // Example 2: |
203 // Example 2: |
169 // "ContendingThread-1" #21 prio=5 os_prio=64 tid=0x00000000007b9000 nid=0x2f waiting for monitor entry [0xfffffd7fc1111000] |
204 // "ContendingThread-1" #21 prio=5 os_prio=64 tid=0x00000000007b9000 nid=0x2f waiting for monitor entry [0xfffffd7fc1111000] |
170 // java.lang.Thread.State: BLOCKED (on object monitor) |
205 // java.lang.Thread.State: BLOCKED (on object monitor) |
171 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:67) |
206 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:140) |
172 // - locked <0xfffffd7e6a2912f8> (a TestThreadDumpMonitorContention$1) |
207 // - locked <0xfffffd7e6a2912f8> (a TestThreadDumpMonitorContention$1) |
|
208 // at java.lang.Thread.run(Thread.java:745) |
|
209 // |
|
210 // Example 3: |
|
211 // "ContendingThread-2" #24 prio=5 os_prio=64 tid=0x0000000000ec8800 nid=0x31 waiting on condition [0xfffffd7bbfffe000] |
|
212 // java.lang.Thread.State: RUNNABLE |
|
213 // JavaThread state: _thread_blocked |
|
214 // Thread: 0x0000000000ec8800 [0x31] State: _at_safepoint _has_called_back 0 _at_poll_safepoint 0 |
|
215 // JavaThread state: _thread_blocked |
|
216 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:140) |
|
217 // - locked <0xfffffd7e6d03eb28> (a TestThreadDumpMonitorContention$1) |
173 // at java.lang.Thread.run(Thread.java:745) |
218 // at java.lang.Thread.run(Thread.java:745) |
174 // |
219 // |
175 static boolean checkLockedLine(String line) { |
220 static boolean checkLockedLine(String line) { |
176 Matcher matcher = LOCK_PATTERN.matcher(line); |
221 Matcher matcher = LOCK_PATTERN.matcher(line); |
177 if (matcher.matches()) { |
222 if (matcher.matches()) { |
178 if (verbose) { |
223 if (verbose) { |
179 System.out.println("locked_line='" + line + "'"); |
224 System.out.println("locked_line='" + line + "'"); |
180 } |
225 } |
181 match_list[match_cnt] = new String(line); |
226 locked_match_list[locked_line_match_cnt] = new String(line); |
182 match_cnt++; |
227 locked_line_match_cnt++; |
183 |
228 |
184 matcher = HEADER_RUNNABLE_PATTERN.matcher(header_line); |
229 matcher = HEADER_RUNNABLE_PATTERN.matcher(header_line); |
185 if (!matcher.matches()) { |
230 if (matcher.matches()) { |
|
231 sum_locked_hdr_runnable_cnt++; |
|
232 } else { |
186 // It's strange, but a locked line can also |
233 // It's strange, but a locked line can also |
187 // match the HEADER_WAITING_PATTERN. |
234 // match the HEADER_WAITING_PATTERN{1,2}. |
188 matcher = HEADER_WAITING_PATTERN.matcher(header_line); |
235 matcher = HEADER_WAITING_PATTERN1.matcher(header_line); |
189 if (!matcher.matches()) { |
236 if (matcher.matches()) { |
190 System.err.println(); |
237 sum_locked_hdr_waiting1_cnt++; |
191 System.err.println("ERROR: header line does " + |
238 } else { |
192 "not match runnable or waiting patterns."); |
239 matcher = HEADER_WAITING_PATTERN2.matcher(header_line); |
193 System.err.println("ERROR: header_line='" + |
240 if (matcher.matches()) { |
194 header_line + "'"); |
241 sum_locked_hdr_waiting2_cnt++; |
195 System.err.println("ERROR: locked_line='" + line + "'"); |
242 } else { |
196 error_cnt++; |
243 System.err.println(); |
|
244 System.err.println("ERROR: header line does " + |
|
245 "not match runnable or waiting patterns."); |
|
246 System.err.println("ERROR: header_line='" + |
|
247 header_line + "'"); |
|
248 System.err.println("ERROR: locked_line='" + line + |
|
249 "'"); |
|
250 error_cnt++; |
|
251 } |
197 } |
252 } |
198 } |
253 } |
199 |
254 |
200 matcher = THREAD_STATE_RUNNABLE_PATTERN.matcher(thread_state_line); |
255 matcher = THREAD_STATE_RUNNABLE_PATTERN.matcher(thread_state_line); |
201 if (!matcher.matches()) { |
256 if (matcher.matches()) { |
|
257 sum_locked_thr_state_runnable_cnt++; |
|
258 } else { |
202 // It's strange, but a locked line can also |
259 // It's strange, but a locked line can also |
203 // match the THREAD_STATE_BLOCKED_PATTERN. |
260 // match the THREAD_STATE_BLOCKED_PATTERN. |
204 matcher = THREAD_STATE_BLOCKED_PATTERN.matcher( |
261 matcher = THREAD_STATE_BLOCKED_PATTERN.matcher( |
205 thread_state_line); |
262 thread_state_line); |
206 if (!matcher.matches()) { |
263 if (matcher.matches()) { |
|
264 sum_locked_thr_state_blocked_cnt++; |
|
265 } else { |
207 System.err.println(); |
266 System.err.println(); |
208 System.err.println("ERROR: thread state line does not " + |
267 System.err.println("ERROR: thread state line does not " + |
209 "match runnable or waiting patterns."); |
268 "match runnable or waiting patterns."); |
210 System.err.println("ERROR: " + "thread_state_line='" + |
269 System.err.println("ERROR: " + "thread_state_line='" + |
211 thread_state_line + "'"); |
270 thread_state_line + "'"); |
227 // Process the waiting line here if we found one. |
286 // Process the waiting line here if we found one. |
228 // |
287 // |
229 // Example: |
288 // Example: |
230 // "ContendingThread-2" #22 prio=5 os_prio=64 tid=0x00000000007b9800 nid=0x30 waiting for monitor entry [0xfffffd7fc1010000] |
289 // "ContendingThread-2" #22 prio=5 os_prio=64 tid=0x00000000007b9800 nid=0x30 waiting for monitor entry [0xfffffd7fc1010000] |
231 // java.lang.Thread.State: BLOCKED (on object monitor) |
290 // java.lang.Thread.State: BLOCKED (on object monitor) |
232 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:67) |
291 // at TestThreadDumpMonitorContention$1.run(TestThreadDumpMonitorContention.java:140) |
233 // - waiting to lock <0xfffffd7e6a2912f8> (a TestThreadDumpMonitorContention$1) |
292 // - waiting to lock <0xfffffd7e6a2912f8> (a TestThreadDumpMonitorContention$1) |
234 // at java.lang.Thread.run(Thread.java:745) |
293 // at java.lang.Thread.run(Thread.java:745) |
235 // |
294 // |
236 static boolean checkWaitingLine(String line) { |
295 static boolean checkWaitingLine(String line) { |
237 Matcher matcher = WAITING_PATTERN.matcher(line); |
296 Matcher matcher = WAITING_PATTERN.matcher(line); |
238 if (matcher.matches()) { |
297 if (matcher.matches()) { |
|
298 waiting_line_match_cnt++; |
239 if (verbose) { |
299 if (verbose) { |
240 System.out.println("waiting_line='" + line + "'"); |
300 System.out.println("waiting_line='" + line + "'"); |
241 } |
301 } |
242 |
302 |
243 matcher = HEADER_WAITING_PATTERN.matcher(header_line); |
303 matcher = HEADER_WAITING_PATTERN1.matcher(header_line); |
244 if (!matcher.matches()) { |
304 if (matcher.matches()) { |
|
305 sum_waiting_hdr_waiting1_cnt++; |
|
306 } else { |
245 System.err.println(); |
307 System.err.println(); |
246 System.err.println("ERROR: header line does " + |
308 System.err.println("ERROR: header line does " + |
247 "not match a waiting pattern."); |
309 "not match a waiting pattern."); |
248 System.err.println("ERROR: header_line='" + header_line + "'"); |
310 System.err.println("ERROR: header_line='" + header_line + "'"); |
249 System.err.println("ERROR: waiting_line='" + line + "'"); |
311 System.err.println("ERROR: waiting_line='" + line + "'"); |
250 error_cnt++; |
312 error_cnt++; |
251 } |
313 } |
252 |
314 |
253 matcher = THREAD_STATE_BLOCKED_PATTERN.matcher(thread_state_line); |
315 matcher = THREAD_STATE_BLOCKED_PATTERN.matcher(thread_state_line); |
254 if (!matcher.matches()) { |
316 if (matcher.matches()) { |
|
317 sum_waiting_thr_state_blocked_cnt++; |
|
318 } else { |
255 System.err.println(); |
319 System.err.println(); |
256 System.err.println("ERROR: thread state line " + |
320 System.err.println("ERROR: thread state line " + |
257 "does not match a waiting pattern."); |
321 "does not match a waiting pattern."); |
258 System.err.println("ERROR: thread_state_line='" + |
322 System.err.println("ERROR: thread_state_line='" + |
259 thread_state_line + "'"); |
323 thread_state_line + "'"); |
288 // - we care about at most three lines from each stack trace |
355 // - we care about at most three lines from each stack trace |
289 // - if both stack traces match LOCKED_PATTERN, then that's |
356 // - if both stack traces match LOCKED_PATTERN, then that's |
290 // a failure and we report it |
357 // a failure and we report it |
291 // - for a stack trace that matches LOCKED_PATTERN, we verify: |
358 // - for a stack trace that matches LOCKED_PATTERN, we verify: |
292 // - the header line matches HEADER_RUNNABLE_PATTERN |
359 // - the header line matches HEADER_RUNNABLE_PATTERN |
293 // or HEADER_WAITING_PATTERN |
360 // or HEADER_WAITING_PATTERN{1,2} |
294 // - the thread state line matches THREAD_STATE_BLOCKED_PATTERN |
361 // - the thread state line matches THREAD_STATE_BLOCKED_PATTERN |
295 // or THREAD_STATE_RUNNABLE_PATTERN |
362 // or THREAD_STATE_RUNNABLE_PATTERN |
296 // - we report any mismatches as failures |
363 // - we report any mismatches as failures |
297 // - for a stack trace that matches WAITING_PATTERN, we verify: |
364 // - for a stack trace that matches WAITING_PATTERN, we verify: |
298 // - the header line matches HEADER_WAITING_PATTERN |
365 // - the header line matches HEADER_WAITING_PATTERN1 |
299 // - the thread state line matches THREAD_STATE_BLOCKED_PATTERN |
366 // - the thread state line matches THREAD_STATE_BLOCKED_PATTERN |
300 // - we report any mismatches as failures |
367 // - we report any mismatches as failures |
301 // - the stack traces that match HEADER_PREFIX_PATTERN may |
368 // - the stack traces that match HEADER_PREFIX_PATTERN may |
302 // not match either LOCKED_PATTERN or WAITING_PATTERN |
369 // not match either LOCKED_PATTERN or WAITING_PATTERN |
303 // because we might observe the thread outside of |
370 // because we might observe the thread outside of |
364 continue; |
432 continue; |
365 } |
433 } |
366 } |
434 } |
367 process.waitFor(); |
435 process.waitFor(); |
368 |
436 |
369 if (match_cnt == 2) { |
437 if (header_prefix_match_cnt != 2) { |
370 if (match_list[0].equals(match_list[1])) { |
438 System.err.println(); |
371 System.err.println(); |
439 System.err.println("ERROR: should match exactly two headers."); |
372 System.err.println("ERROR: matching lock lines:"); |
440 System.err.println("ERROR: header_prefix_match_cnt=" + |
373 System.err.println("ERROR: line[0]'" + match_list[0] + "'"); |
441 header_prefix_match_cnt); |
374 System.err.println("ERROR: line[1]'" + match_list[1] + "'"); |
442 error_cnt++; |
375 error_cnt++; |
443 } |
376 } |
444 |
377 } |
445 if (locked_line_match_cnt == 2) { |
|
446 if (locked_match_list[0].equals(locked_match_list[1])) { |
|
447 System.err.println(); |
|
448 System.err.println("ERROR: matching lock lines:"); |
|
449 System.err.println("ERROR: line[0]'" + |
|
450 locked_match_list[0] + "'"); |
|
451 System.err.println("ERROR: line[1]'" + |
|
452 locked_match_list[1] + "'"); |
|
453 error_cnt++; |
|
454 } |
|
455 } |
|
456 |
|
457 if (locked_line_match_cnt == 1) { |
|
458 // one thread has the lock |
|
459 if (waiting_line_match_cnt == 1) { |
|
460 // and the other contended for it |
|
461 sum_contended_cnt++; |
|
462 } else { |
|
463 // and the other is just running |
|
464 sum_uncontended_cnt++; |
|
465 } |
|
466 } else if (waiting_line_match_cnt == 1) { |
|
467 // one thread is waiting |
|
468 sum_one_waiting_cnt++; |
|
469 } else if (waiting_line_match_cnt == 2) { |
|
470 // both threads are waiting |
|
471 sum_both_waiting_cnt++; |
|
472 } else { |
|
473 // both threads are running |
|
474 sum_both_running_cnt++; |
|
475 } |
378 |
476 |
379 // slight delay between jstack launches |
477 // slight delay between jstack launches |
380 Thread.sleep(500); |
478 Thread.sleep(500); |
|
479 } |
|
480 |
|
481 if (error_cnt != 0) { |
|
482 // skip summary info since there were errors |
|
483 return; |
|
484 } |
|
485 |
|
486 System.out.println("INFO: Summary for all samples:"); |
|
487 System.out.println("INFO: both_running_cnt=" + sum_both_running_cnt); |
|
488 System.out.println("INFO: both_waiting_cnt=" + sum_both_waiting_cnt); |
|
489 System.out.println("INFO: contended_cnt=" + sum_contended_cnt); |
|
490 System.out.println("INFO: one_waiting_cnt=" + sum_one_waiting_cnt); |
|
491 System.out.println("INFO: uncontended_cnt=" + sum_uncontended_cnt); |
|
492 System.out.println("INFO: locked_hdr_runnable_cnt=" + |
|
493 sum_locked_hdr_runnable_cnt); |
|
494 System.out.println("INFO: locked_hdr_waiting1_cnt=" + |
|
495 sum_locked_hdr_waiting1_cnt); |
|
496 System.out.println("INFO: locked_hdr_waiting2_cnt=" + |
|
497 sum_locked_hdr_waiting2_cnt); |
|
498 System.out.println("INFO: locked_thr_state_blocked_cnt=" + |
|
499 sum_locked_thr_state_blocked_cnt); |
|
500 System.out.println("INFO: locked_thr_state_runnable_cnt=" + |
|
501 sum_locked_thr_state_runnable_cnt); |
|
502 System.out.println("INFO: waiting_hdr_waiting1_cnt=" + |
|
503 sum_waiting_hdr_waiting1_cnt); |
|
504 System.out.println("INFO: waiting_thr_state_blocked_cnt=" + |
|
505 sum_waiting_thr_state_blocked_cnt); |
|
506 |
|
507 if (sum_contended_cnt == 0) { |
|
508 System.err.println("WARNING: the primary scenario for 8036823" + |
|
509 " has not been exercised by this test run."); |
381 } |
510 } |
382 } |
511 } |
383 |
512 |
384 // This helper relies on RuntimeMXBean.getName() returning a string |
513 // This helper relies on RuntimeMXBean.getName() returning a string |
385 // that looks like this: 5436@mt-haku |
514 // that looks like this: 5436@mt-haku |