001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.server;
042:
043: import org.netbeans.lib.profiler.global.CommonConstants;
044: import org.netbeans.lib.profiler.global.Platform;
045: import org.netbeans.lib.profiler.server.system.Classes;
046: import org.netbeans.lib.profiler.server.system.GC;
047: import org.netbeans.lib.profiler.server.system.Threads;
048: import org.netbeans.lib.profiler.server.system.Timers;
049: import org.netbeans.lib.profiler.wireprotocol.MethodNamesResponse;
050: import org.netbeans.lib.profiler.wireprotocol.MonitoredNumbersResponse;
051: import org.netbeans.lib.profiler.wireprotocol.Response;
052: import java.util.ArrayList;
053: import java.util.List;
054: import java.util.Vector;
055:
056: /**
057: * Implementation of the monitoring functionality, active throughout the TA execution.
058: *
059: * @author Tomas Hurka
060: * @author Misha Dmitriev
061: * @author Ian Formanek
062: */
063: public class Monitors implements CommonConstants {
064: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
065:
066: static class LongList {
067: //~ Instance fields ------------------------------------------------------------------------------------------------------
068:
069: long[] data;
070: int size;
071:
072: //~ Constructors ---------------------------------------------------------------------------------------------------------
073:
074: LongList(int size) {
075: data = new long[size];
076: }
077:
078: //~ Methods --------------------------------------------------------------------------------------------------------------
079:
080: long[] getArray() {
081: long[] arr = new long[size];
082: System.arraycopy(data, 0, arr, 0, size);
083:
084: return arr;
085: }
086:
087: void add(long l) {
088: ensureSize();
089: data[size++] = l;
090: }
091:
092: void clear() {
093: size = 0;
094: }
095:
096: void ensureSize() {
097: if (size >= data.length) {
098: int newCapacity = ((size * 3) / 2) + 1;
099: long[] elementData = new long[newCapacity];
100: System.arraycopy(data, 0, elementData, 0, size);
101: data = elementData;
102: }
103: }
104: }
105:
106: static class SurvGenAndThreadsMonitor extends Thread {
107: //~ Instance fields ------------------------------------------------------------------------------------------------------
108:
109: public boolean started;
110: public boolean terminated;
111: private LongList gcFinishs;
112: private LongList gcStarts;
113: private ThreadDataTable threadTable;
114: private Vector markerObjects;
115: private int[] allThreadStatusRough;
116:
117: // ---------------------------------- Thread status data management -------------------------------------
118: private Thread[] allThreadsRough;
119:
120: // -------------------------------- Surviving generations data management ---------------------------------
121: private int savedGCEpoch;
122: private long lastGCFinish;
123: private long lastGCStart;
124: private long time; // Used just for estimating the overhead
125: private long time0; // Used just for estimating the overhead
126:
127: //~ Constructors ---------------------------------------------------------------------------------------------------------
128:
129: SurvGenAndThreadsMonitor() {
130: super ("*** JFluid Monitor thread ***"); // NOI18N
131: savedGCEpoch = GC.getCurrentGCEpoch();
132: markerObjects = new Vector();
133: allThreadsRough = new Thread[20];
134: allThreadStatusRough = new int[20];
135: threadTable = new ThreadDataTable();
136: gcStarts = new LongList(16);
137: gcFinishs = new LongList(16);
138: setPriority(Thread.MAX_PRIORITY);
139: setDaemon(true);
140: }
141:
142: //~ Methods --------------------------------------------------------------------------------------------------------------
143:
144: public int getNSurvGen() {
145: return markerObjects.size();
146: }
147:
148: public synchronized void getThreadsData(
149: MonitoredNumbersResponse mresp) {
150: //threadTable.printCurrentStatus();
151: threadTable.getThreadsData(mresp);
152: threadTable.resetStates();
153: }
154:
155: public long getTime() {
156: long ret = time;
157: time = 0;
158:
159: return ret;
160: }
161:
162: public void run() {
163: started = true;
164:
165: int checkForUnloadedClassesCounter = 3;
166:
167: while (!terminated) {
168: //time0 = ProfilerRuntime.getCurrentTimeInCounts();
169: updateSurvGenData();
170: updateThreadsData();
171: updateGCStartFinishData();
172:
173: // The next call has nothing to do with monitoring - it's "housekeeping". See details in ClassLoaderManager.
174: if (--checkForUnloadedClassesCounter == 0) {
175: ClassLoaderManager.checkForUnloadedClasses();
176: checkForUnloadedClassesCounter = 3;
177: }
178:
179: ThreadInfo.releaseDeadThreads();
180:
181: //time += (ProfilerRuntime.getCurrentTimeInCounts() - time0);
182: try {
183: Thread.sleep(100);
184: } catch (Exception ex) {
185: }
186: }
187: }
188:
189: synchronized void updateGCStartFinishData() {
190: int i;
191: long maxStart = lastGCStart;
192: long maxFinish = lastGCFinish;
193: GC.getGCStartFinishTimes(gcStartTimes, gcFinishTimes);
194:
195: for (i = 0; i < GC.OBSERVED_PERIODS; i++) {
196: long start = gcStartTimes[i];
197: long finish = gcFinishTimes[i];
198:
199: if (start > lastGCStart) {
200: gcStarts.add(start & 0xFFFFFFFFFFFFFFL); // we use only 7 bytes for hi res timer
201: maxStart = start;
202: }
203:
204: if (finish > lastGCFinish) {
205: gcFinishs.add(finish & 0xFFFFFFFFFFFFFFL); // we use only 7 bytes for hi res timer
206: maxFinish = finish;
207: }
208: }
209:
210: lastGCStart = maxStart;
211: lastGCFinish = maxFinish;
212: }
213:
214: private synchronized void getGCStartFinishData(
215: MonitoredNumbersResponse resp) {
216: long[] start = gcStarts.getArray();
217: long[] finish = gcFinishs.getArray();
218:
219: gcStarts.clear();
220: gcFinishs.clear();
221:
222: resp.setGCstartFinishData(start, finish);
223: }
224:
225: private void updateSurvGenData() {
226: // Compare the previously saved GC epoch value with the current one, and allocate new marker if it changed
227: int currentGCEpoch = GC.getCurrentGCEpoch();
228:
229: if (currentGCEpoch != savedGCEpoch) {
230: //System.out.println("*** GC epoch changed from " + savedGCEpoch + " to " + currentGCEpoch);
231: markerObjects.add(new Object());
232: savedGCEpoch = currentGCEpoch;
233:
234: // Walk through the markers, determine which ones are adjacent, etc.
235: int lenMinusOne = markerObjects.size() - 1;
236: int initSize = lenMinusOne + 1;
237:
238: for (int i = 0; i < lenMinusOne; i++) {
239: if (GC.objectsAdjacent(markerObjects.get(i),
240: markerObjects.get(i + 1))) {
241: //System.out.println("********* Objects adjacent at i = " + i + ": " + markerObjects.get(i) + " and " + markerObjects.get(i+1));
242: markerObjects.remove(i);
243: i--;
244: lenMinusOne--;
245: }
246: }
247: }
248: }
249:
250: private void updateThreadsData() {
251: Thread tMain = ProfilerServer
252: .isTargetAppMainThreadComplete() ? ProfilerServer
253: .getMainThread() : null;
254:
255: allThreadsRough = Threads.getAllThreads(allThreadsRough);
256:
257: if (allThreadStatusRough.length < allThreadsRough.length) {
258: allThreadStatusRough = new int[allThreadsRough.length];
259: }
260:
261: Threads.getThreadsStatus(allThreadsRough,
262: allThreadStatusRough);
263:
264: if (DEBUG) {
265: for (int i = 0; i < allThreadsRough.length; i++) {
266: if (allThreadsRough[i] == null) {
267: break;
268: }
269:
270: System.err
271: .println("org.netbeans.lib.profiler.server.Monitors.DEBUG: " // NOI18N
272: + allThreadsRough[i].getName()
273: + ", status: "
274: + allThreadStatusRough[i]); // NOI18N
275: }
276: }
277:
278: synchronized (this ) {
279: threadTable.prePut();
280:
281: for (int i = 0; i < allThreadsRough.length; i++) {
282: Thread thread = allThreadsRough[i];
283:
284: if (thread == null) {
285: break; // No more live threads in this array
286: }
287:
288: // We check if the thread is our own using ThreadInfo.isProfilerServerThread(), not its name
289: // (which looks easier), because it turns out that Thread.getName() creates a new String out of internal
290: // char[] array every time. ThreadInfo check should be just faster.
291: if ((thread == this )
292: || (thread == tMain)
293: || ThreadInfo
294: .isProfilerServerThread(thread)) {
295: //System.err.println("Skipping thread "+i+", tMain:"+tMain +", thread: "+thread);
296: continue;
297: }
298:
299: threadTable.put(thread, allThreadStatusRough[i]);
300: }
301:
302: threadTable.incStatusIdx();
303: }
304:
305: if (DEBUG) {
306: System.err.println("Final thread table: "); // NOI18N
307: threadTable.printCurrentStatus();
308: }
309: }
310: }
311:
312: static class ThreadDataTable {
313: //~ Static fields/initializers -------------------------------------------------------------------------------------------
314:
315: private static final int INITIAL_SIZE = 23;
316: private static final int INITIAL_NSTATES = 20;
317: private static Object dummyObj = new Object();
318:
319: //~ Instance fields ------------------------------------------------------------------------------------------------------
320:
321: private String[] newThreadClassNames = new String[INITIAL_SIZE];
322: private int[] newThreadIds = new int[INITIAL_SIZE];
323: private String[] newThreadNames = new String[INITIAL_SIZE];
324: private long[] packedStateTimestamps = new long[INITIAL_NSTATES];
325: private int[] packedThreadIds = new int[INITIAL_SIZE];
326: private byte[] packedThreadStates = new byte[INITIAL_SIZE
327: * INITIAL_NSTATES];
328: private long[] stateSampleTimestamps;
329: private int[] threadIds;
330: private boolean[] threadNew;
331: private byte[][] threadStates;
332: private Object[] threads;
333: private boolean jvmSupportsThreadSleepingState;
334: private int curStateIdx;
335: private int curThreadId;
336: private int nFilledSlots;
337: private int nNewThreads;
338: private int nStates;
339: private int nThreads;
340: private int size;
341: private int threshold;
342:
343: //~ Constructors ---------------------------------------------------------------------------------------------------------
344:
345: ThreadDataTable() {
346: size = INITIAL_SIZE;
347: threshold = (size * 3) / 4;
348: curThreadId = 1;
349: nStates = INITIAL_NSTATES;
350: threads = new Object[size];
351: threadIds = new int[size];
352: threadStates = new byte[size][nStates];
353: stateSampleTimestamps = new long[nStates];
354: threadNew = new boolean[size];
355: nThreads = 0;
356: nFilledSlots = 0;
357: curStateIdx = 0;
358: jvmSupportsThreadSleepingState = Platform
359: .this VMSupportsThreadSleepingStateMonitoring();
360: }
361:
362: //~ Methods --------------------------------------------------------------------------------------------------------------
363:
364: public void getThreadsData(MonitoredNumbersResponse mresp) {
365: if (nThreads > packedThreadIds.length) {
366: packedThreadIds = new int[nThreads];
367: }
368:
369: if (curStateIdx > packedStateTimestamps.length) {
370: packedStateTimestamps = new long[curStateIdx];
371: }
372:
373: int totalStates = nThreads * curStateIdx;
374:
375: if (totalStates > packedThreadStates.length) {
376: packedThreadStates = new byte[totalStates];
377: }
378:
379: int idx0 = 0;
380: int idx1 = 0;
381:
382: for (int i = 0; i < size; i++) {
383: if ((threads[i] != null) && (threads[i] != dummyObj)) {
384: packedThreadIds[idx0++] = threadIds[i];
385: System.arraycopy(threadStates[i], 0,
386: packedThreadStates, idx1, curStateIdx);
387: idx1 += curStateIdx;
388: }
389: }
390:
391: System.arraycopy(stateSampleTimestamps, 0,
392: packedStateTimestamps, 0, curStateIdx);
393:
394: mresp.setDataOnThreads(nThreads, curStateIdx,
395: packedThreadIds, packedStateTimestamps,
396: packedThreadStates);
397:
398: if (nNewThreads > 0) {
399: if (nNewThreads > newThreadIds.length) {
400: newThreadIds = new int[nNewThreads];
401: newThreadNames = new String[nNewThreads];
402: newThreadClassNames = new String[nNewThreads];
403: }
404:
405: int idx = 0;
406:
407: for (int i = 0; i < size; i++) {
408: if (threadNew[i]) {
409: newThreadIds[idx] = threadIds[i];
410: newThreadNames[idx] = ((Thread) threads[i])
411: .getName();
412: newThreadClassNames[idx] = ((Thread) threads[i])
413: .getClass().getName();
414: idx++;
415: }
416: }
417:
418: mresp.setDataOnNewThreads(nNewThreads, newThreadIds,
419: newThreadNames, newThreadClassNames);
420: }
421: }
422:
423: public void incStatusIdx() {
424: curStateIdx++;
425:
426: if (curStateIdx == nStates) {
427: growStatesArrays();
428: }
429: }
430:
431: /**
432: * Sets current status for all threads to dead. Subsequent calls to put will change the status of live threads appropriately.
433: * The rest of the threads for which status will remain dead, are really dead.
434: */
435: public void prePut() {
436: for (int i = 0; i < size; i++) {
437: threadStates[i][curStateIdx] = 0;
438: }
439:
440: stateSampleTimestamps[curStateIdx] = System
441: .currentTimeMillis();
442: }
443:
444: /** Debugging support */
445: public void printCurrentStatus() {
446: for (int i = 0; i < size; i++) {
447: if ((threads[i] == null) || (threads[i] == dummyObj)) {
448: continue;
449: }
450:
451: System.err
452: .print(((Thread) threads[i]).getName() + " "); // NOI18N
453:
454: byte[] states = threadStates[i];
455:
456: for (int j = 0; j < curStateIdx; j++) {
457: System.err.print(states[j]);
458: }
459:
460: System.err.println();
461: }
462:
463: System.err.println();
464: }
465:
466: public void put(Thread thread, int status) {
467: int pos = (thread.hashCode() & 0x7FFFFFFF) % size;
468:
469: while ((threads[pos] != thread) && (threads[pos] != null)) {
470: pos = (pos + 1) % size;
471: }
472:
473: if (threads[pos] == null) {
474: threadNew[pos] = true;
475: threads[pos] = thread;
476: threadIds[pos] = curThreadId++;
477: nThreads++;
478: nNewThreads++;
479: nFilledSlots++;
480: }
481:
482: threadStates[pos][curStateIdx] = (byte) status;
483:
484: if (nFilledSlots > threshold) {
485: growTable();
486: }
487: }
488:
489: public void resetStates() {
490: nNewThreads = 0;
491:
492: if (curStateIdx == 0) {
493: return;
494: }
495:
496: int idx = curStateIdx - 1;
497:
498: for (int i = 0; i < size; i++) {
499: if ((threads[i] == null) || (threads[i] == dummyObj)) {
500: continue;
501: }
502:
503: threadNew[i] = false;
504:
505: if (threadStates[i][idx] == 0) { // Thread is dead
506: threads[i] = dummyObj;
507: nThreads--;
508: }
509: }
510:
511: curStateIdx = 0;
512: }
513:
514: private void growStatesArrays() {
515: int oldNStates = nStates;
516: nStates = nStates * 2;
517:
518: for (int i = 0; i < size; i++) {
519: byte[] oldStates = threadStates[i];
520: threadStates[i] = new byte[nStates];
521: System.arraycopy(oldStates, 0, threadStates[i], 0,
522: oldNStates);
523: }
524:
525: long[] oldTimestamps = stateSampleTimestamps;
526: stateSampleTimestamps = new long[nStates];
527: System.arraycopy(oldTimestamps, 0, stateSampleTimestamps,
528: 0, oldNStates);
529: }
530:
531: private void growTable() {
532: int oldSize = size;
533: size = (((nThreads * 4) / 3) * 2) + 1;
534:
535: if (size < oldSize) {
536: size = oldSize; // Too many threads are dead; get rid of them without growing the table
537: // Otherwise, if the application generates lots of short-lived threads, we may probably end up growing the table
538: // endlessly without reason
539: }
540:
541: threshold = (size * 3) / 4;
542:
543: Object[] oldThreads = threads;
544: int[] oldThreadIds = threadIds;
545: byte[][] oldThreadStates = threadStates;
546: boolean[] oldThreadNew = threadNew;
547: threads = new Object[size];
548: threadIds = new int[size];
549: threadStates = new byte[size][];
550: threadNew = new boolean[size];
551:
552: for (int i = 0; i < oldSize; i++) {
553: if ((oldThreads[i] == null)
554: || (oldThreads[i] == dummyObj)) {
555: continue;
556: }
557:
558: Object thread = oldThreads[i];
559: int pos = (thread.hashCode() & 0x7FFFFFFF) % size;
560:
561: while (threads[pos] != null) {
562: pos = (pos + 1) % size;
563: }
564:
565: threadNew[pos] = oldThreadNew[i];
566: threads[pos] = thread;
567: threadIds[pos] = oldThreadIds[i];
568: threadStates[pos] = oldThreadStates[i];
569: }
570:
571: for (int i = 0; i < size; i++) {
572: if (threadStates[i] == null) {
573: threadStates[i] = new byte[nStates];
574: }
575: }
576:
577: nFilledSlots = nThreads;
578: }
579: }
580:
581: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
582:
583: private static final boolean DEBUG = false;
584: protected static Runtime runtime;
585: protected static SurvGenAndThreadsMonitor stMonitor;
586: protected static long[] generalMNums;
587: protected static long[] gcRelTime;
588: protected static long[] gcStartTimes;
589: protected static long[] gcFinishTimes;
590: protected static long time; // Used just for estimating the overhead
591:
592: //~ Methods ------------------------------------------------------------------------------------------------------------------
593:
594: public static MonitoredNumbersResponse getMonitoredNumbers() {
595: //time = ProfilerRuntime.getCurrentTimeInCounts();
596: int nUserThreads;
597: int nTotalThreads = Threads.getTotalNumberOfThreads();
598: nTotalThreads--; // ProfilerServer.executeInSeparateThread()
599: // This number includes our Server communication thread, memory monitor thread, and separate command execution thread
600:
601: int nSystemThreads = ProfilerInterface
602: .getNPrerecordedSystemThreads();
603:
604: if (nSystemThreads != -1) { // "Start from tool" mode, so we have recorded system threads
605: nUserThreads = nTotalThreads - nSystemThreads;
606: nSystemThreads -= 2; // To hide our own Server and Memory monitor threads
607:
608: if (ProfilerServer.isTargetAppMainThreadComplete()) { // It's not really complete, but executes JFluid code, so logically it's done.
609: nUserThreads--;
610: }
611: } else { // Attachment mode, no exact knowledge of the number of system threads
612: nUserThreads = nTotalThreads - 3; // At least we know that two threads are JFluid-owned
613: }
614:
615: // Now compensate for an additonal Java thread used in the following two instrumentation modes
616: int instrType = ProfilerInterface.getCurrentInstrType();
617:
618: if ((instrType == INSTR_RECURSIVE_SAMPLED)
619: || (instrType == INSTR_OBJECT_LIVENESS)) {
620: nUserThreads--;
621: }
622:
623: // Get the relative GC time metrics
624: GC.getGCRelativeTimeMetrics(gcRelTime);
625:
626: generalMNums[MonitoredNumbersResponse.FREE_MEMORY_IDX] = runtime
627: .freeMemory();
628: generalMNums[MonitoredNumbersResponse.TOTAL_MEMORY_IDX] = runtime
629: .totalMemory();
630: generalMNums[MonitoredNumbersResponse.USER_THREADS_IDX] = nUserThreads;
631: generalMNums[MonitoredNumbersResponse.SYSTEM_THREADS_IDX] = nSystemThreads;
632: generalMNums[MonitoredNumbersResponse.SURVIVING_GENERATIONS_IDX] = stMonitor
633: .getNSurvGen();
634: generalMNums[MonitoredNumbersResponse.GC_TIME_IDX] = gcRelTime[0];
635: generalMNums[MonitoredNumbersResponse.GC_PAUSE_IDX] = gcRelTime[1];
636: generalMNums[MonitoredNumbersResponse.LOADED_CLASSES_IDX] = Classes
637: .getLoadedClassCount();
638: generalMNums[MonitoredNumbersResponse.TIMESTAMP_IDX] = System
639: .currentTimeMillis();
640:
641: MonitoredNumbersResponse resp = new MonitoredNumbersResponse(
642: generalMNums);
643: stMonitor.getThreadsData(resp);
644: stMonitor.getGCStartFinishData(resp);
645:
646: //showTime();
647: return resp;
648: }
649:
650: public static void initialize() {
651: runtime = Runtime.getRuntime();
652: gcRelTime = new long[2];
653: gcStartTimes = new long[GC.OBSERVED_PERIODS];
654: gcFinishTimes = new long[GC.OBSERVED_PERIODS];
655: generalMNums = new long[MonitoredNumbersResponse.GENERAL_NUMBERS_SIZE];
656: GC.activateGCEpochCounter(true);
657: stMonitor = new SurvGenAndThreadsMonitor();
658: ThreadInfo.addProfilerServerThread(stMonitor);
659: stMonitor.start();
660: }
661:
662: /** Check if all monitor threads have been started */
663: public static boolean monitorThreadsStarted() {
664: return stMonitor.started;
665: }
666:
667: public static void shutdown() {
668: stMonitor.terminated = true;
669: }
670:
671: /** Measures and prints the overhead of the monitoring code */
672: private static void showTime() {
673: long cnts = Timers.getNoOfCountsInSecond();
674: time = ((Timers.getCurrentTimeInCounts() - time) * 1000000)
675: / cnts;
676:
677: long time1 = (stMonitor.getTime() * 1000000) / cnts;
678: System.out.println("!!! time = " + time + ", time1 = " + time1
679: + ", sum = " + (time + time1)); // NOI18N
680: // Originally (with no thread state information) time returned above would be 60..80 + 600..700 microsec
681: // If getMonitoredNumbers() is called roughly once a second, this translates into 750/1000000*100 = 0.075 per cent overhead
682: // With thread state information, overhead grows to about 60..80 + 1000 microsec. Still just 0.1 per cent overhead.
683: // Transport, and, more importantly, client-side processing, likely take much more time.
684: }
685: }
|