1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package jdk.internal.vm;
 26 
 27 import java.nio.charset.StandardCharsets;
 28 import java.util.Iterator;
 29 import java.util.List;
 30 import java.util.Map;
 31 import java.util.stream.Collectors;
 32 import java.util.stream.IntStream;
 33 import jdk.internal.access.JavaLangAccess;
 34 import jdk.internal.access.SharedSecrets;
 35 import sun.nio.ch.Poller;
 36 
 37 /**
 38  * The implementation for the jcmd Thread.vthread_summary diagnostic command.
 39  */
 40 public class VThreadSummary {
 41     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 42 
 43     // maximum number of thread containers to print
 44     private static final int MAX_THREAD_CONTAINERS = 256;
 45 
 46     // set to true if I/O poller in use
 47     private static volatile boolean pollerInitialized;
 48 
 49     private VThreadSummary() { }
 50 
 51     /**
 52      * Invoked by the poller I/O mechanism when it initializes.
 53      */
 54     public static void pollerInitialized() {
 55         pollerInitialized = true;
 56     }
 57 
 58     /**
 59      * Invoked by the VM to print virtual thread summary information.
 60      * @return the UTF-8 encoded information to print
 61      */
 62     private static byte[] print() {
 63         StringBuilder sb = new StringBuilder();
 64 
 65         // print thread containers (thread groupings)
 66         new ThreadContainersPrinter(sb, MAX_THREAD_CONTAINERS).run();
 67         sb.append(System.lineSeparator());
 68 
 69         // print virtual thread scheduler
 70         printSchedulerInfo(sb);
 71         sb.append(System.lineSeparator());
 72 
 73         // print I/O pollers if initialized
 74         if (pollerInitialized) {
 75             printPollerInfo(sb);
 76         }
 77 
 78         return sb.toString().getBytes(StandardCharsets.UTF_8);
 79     }
 80 
 81     /**
 82      * Prints the tree of thread containers starting from the root container.
 83      */
 84     private static class ThreadContainersPrinter {
 85         private final StringBuilder sb;
 86         private final int max;
 87         private int count;
 88 
 89         ThreadContainersPrinter(StringBuilder sb, int max) {
 90             this.sb = sb;
 91             this.max = max;
 92         }
 93 
 94         void run() {
 95             printThreadContainers(ThreadContainers.root(), 0);
 96         }
 97 
 98         /**
 99          * Prints the given thread container and its children.
100          * @return true if the thread container and all its children were printed,
101          *   false if the output was truncated because the max was reached
102          */
103         private boolean printThreadContainers(ThreadContainer container, int depth) {
104             if (!printThreadContainer(container, depth)) {
105                 return false;
106             }
107             boolean truncated = container.children()
108                     .map(c -> printThreadContainers(c, depth + 1))
109                     .anyMatch(b -> b == false);
110             return !truncated;
111         }
112 
113         /**
114          * Prints the given thread container or a "truncated" message if the maximum
115          * number of thread containers has already been printed.
116          * @param container the thread container
117          * @param depth the depth in the tree, for indentation purposes
118          * @return true if the thread container was printed, false if beyond max
119          */
120         private boolean printThreadContainer(ThreadContainer container, int depth) {
121             count++;
122             if (count > max) {
123                 sb.append("<truncated ...>")
124                         .append(System.lineSeparator());
125                 return false;
126             }
127 
128             Map<Boolean, Long> threadCounts = container.threads()
129                     .collect(Collectors.partitioningBy(Thread::isVirtual, Collectors.counting()));
130             long platformThreadCount = threadCounts.get(Boolean.FALSE);
131             long virtualThreadCount = threadCounts.get(Boolean.TRUE);
132             if (depth > 0) {
133                 int indent = depth * 4;
134                 sb.append(" ".repeat(indent)).append("+-- ");
135             }
136             sb.append(container)
137                     .append(" [platform threads = ")
138                     .append(platformThreadCount)
139                     .append(", virtual threads = ")
140                     .append(virtualThreadCount)
141                     .append("]")
142                     .append(System.lineSeparator());
143 
144             return true;
145         }
146     }
147 
148     /**
149      * Print information on the virtual thread schedulers to given string buffer.
150      */
151     static void printSchedulerInfo(StringBuilder sb) {
152         sb.append("Default virtual thread scheduler:")
153                 .append(System.lineSeparator());
154         sb.append(JLA.virtualThreadDefaultScheduler())
155                 .append(System.lineSeparator());
156 
157         sb.append(System.lineSeparator());
158 
159         sb.append("Timeout schedulers:")
160                 .append(System.lineSeparator());
161         var schedulers = JLA.virtualThreadDelayedTaskSchedulers().toList();
162         for (int i = 0; i < schedulers.size(); i++) {
163             sb.append('[')
164                     .append(i)
165                     .append("] ")
166                     .append(schedulers.get(i))
167                     .append(System.lineSeparator());
168         }
169     }
170 
171     /**
172      * Print information on threads registered for I/O to the given string buffer.
173      */
174     private static void printPollerInfo(StringBuilder sb) {
175         Poller masterPoller = Poller.masterPoller();
176         List<Poller> readPollers = Poller.readPollers();
177         List<Poller> writePollers = Poller.writePollers();
178 
179         if (masterPoller != null) {
180             sb.append("Master I/O poller:")
181                     .append(System.lineSeparator())
182                     .append(masterPoller)
183                     .append(System.lineSeparator());
184 
185             sb.append(System.lineSeparator());
186         }
187 
188         sb.append("Read I/O pollers:");
189         sb.append(System.lineSeparator());
190         IntStream.range(0, readPollers.size())
191                 .forEach(i -> sb.append('[')
192                         .append(i)
193                         .append("] ")
194                         .append(readPollers.get(i))
195                         .append(System.lineSeparator()));
196 
197         sb.append(System.lineSeparator());
198 
199         sb.append("Write I/O pollers:");
200         sb.append(System.lineSeparator());
201         IntStream.range(0, writePollers.size())
202                 .forEach(i -> sb.append('[')
203                         .append(i)
204                         .append("] ")
205                         .append(writePollers.get(i))
206                         .append(System.lineSeparator()));
207     }
208 }