001    /*
002     *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003     *
004     *  This file is licensed to You under the Eclipse Public License (EPL);
005     *  You may not use this file except in compliance with the License. You
006     *  may obtain a copy of the License at
007     *
008     *      http://www.opensource.org/licenses/eclipse-1.0.php
009     *
010     *  See the COPYRIGHT.txt file distributed with this work for information
011     *  regarding copyright ownership.
012     */
013    package org.jikesrvm.adaptive.database.methodsamples;
014    
015    import java.util.ArrayList;
016    import java.util.List;
017    
018    import org.jikesrvm.VM;
019    import org.jikesrvm.adaptive.controller.Controller;
020    import org.jikesrvm.adaptive.controller.HotMethodEvent;
021    import org.jikesrvm.adaptive.controller.HotMethodRecompilationEvent;
022    import org.jikesrvm.adaptive.measurements.Reportable;
023    import org.jikesrvm.adaptive.util.AOSLogging;
024    import org.jikesrvm.classloader.RVMMethod;
025    import org.jikesrvm.compilers.common.CompiledMethod;
026    import org.jikesrvm.compilers.common.CompiledMethods;
027    import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
028    import org.jikesrvm.scheduler.RVMThread;
029    
030    /**
031     * A container for recording how often a method is executed.
032     */
033    public final class MethodCountData implements Reportable {
034    
035      private static final boolean DEBUG = false;
036    
037      /**
038       * Sum of values in count array.
039       */
040      private double totalCountsTaken;
041    
042      /**
043       * Count array: counts how many times a method is executed.
044       * Constraint: counts[0] is not used.
045       */
046      private double[] counts;
047      /**
048       * Maps count array index to compiled method id.
049       * Constraint: cmids[0] is not used.
050       */
051      private int[] cmids;
052      /**
053       * Maps compiled method id to count array index.
054       * '0' implies that there is no entry in the count array for this cmid
055       */
056      private int[] map;
057      /**
058       * Next available count array entry.
059       */
060      private int nextIndex;
061    
062      /**
063       * Constructor
064       */
065      public MethodCountData() {
066        initialize();
067      }
068    
069      /**
070       * Reset fields.
071       */
072      private void initialize() {
073        int numCompiledMethods = CompiledMethods.numCompiledMethods();
074        map = new int[numCompiledMethods + (numCompiledMethods >>> 2)];
075        counts = new double[256];
076        cmids = new int[256];
077        nextIndex = 1;
078        totalCountsTaken = 0;
079      }
080    
081      /**
082       * Drain a buffer of compiled method id's and update the count array.
083       *
084       * @param countBuffer a buffer of compiled method id's
085       * @param numCounts the number of valid entries in the buffer
086       */
087      public synchronized void update(int[] countBuffer, int numCounts) {
088        for (int i = 0; i < numCounts; i++) {
089          int cmid = countBuffer[i];
090          int index = findOrCreateHeapIdx(cmid);
091          counts[index]++;      // Record count
092          heapifyUp(index);     // Fix up the heap
093        }
094        totalCountsTaken += numCounts;
095        if (DEBUG) validityCheck();
096      }
097    
098      /**
099       * Increment the count for a compiled method id.
100       *
101       * @param cmid compiled method id
102       * @param numCounts number of counts
103       */
104      public synchronized void update(int cmid, double numCounts) {
105        int index = findOrCreateHeapIdx(cmid);
106        counts[index] += numCounts;       // Record counts
107        heapifyUp(index);                 // Fix up the heap
108        totalCountsTaken += numCounts;
109        if (DEBUG) validityCheck();
110      }
111    
112      /**
113       *  Print the counted (nonzero) methods.
114       *  To get a sorted list, pipe the output through sort -n -r.
115       */
116      @Override
117      public synchronized void report() {
118        RVMThread.dumpLock.lockNoHandshake();
119        VM.sysWrite("Method counts: A total of " + totalCountsTaken + " samples\n");
120        for (int i = 1; i < nextIndex; i++) {
121          double percent = 100 * countsToHotness(counts[i]);
122          CompiledMethod cm = CompiledMethods.getCompiledMethod(cmids[i]);
123          VM.sysWrite(counts[i] + " (" + percent + "%) ");
124          if (cm == null) {
125            VM.sysWriteln("OBSOLETE");                // Compiled Method Obsolete
126          } else {
127            if (cm.getCompilerType() == CompiledMethod.TRAP) {
128              VM.sysWriteln("<Hardware Trap Frame>");
129            } else {
130              RVMMethod m = cm.getMethod();
131              VM.sysWrite(m);
132              if (m.getDeclaringClass().isInBootImage()) {
133                VM.sysWrite("\tBOOT");
134              }
135            }
136            VM.sysWriteln();
137          }
138        }
139        RVMThread.dumpLock.unlock();
140      }
141    
142      /**
143       * @return the total number of samples taken
144       */
145      public double getTotalNumberOfSamples() {
146        return totalCountsTaken;
147      }
148    
149      /**
150       * Reset (clear) the method counts
151       */
152      @Override
153      public synchronized void reset() {
154        initialize();
155      }
156    
157      /**
158       * Get the current count for a given compiled method id.
159       *
160       * @param cmid compiled method id
161       */
162      public synchronized double getData(int cmid) {
163        int index = findHeapIdx(cmid);
164        if (index > 0) {
165          return counts[index];
166        } else {
167          return 0.0;
168        }
169      }
170    
171      /**
172       * Reset (set to 0.0) the count for a given compiled method id.
173       *
174       * @param cmid compiled method id
175       */
176      public synchronized void reset(int cmid) {
177        int index = findHeapIdx(cmid);
178        if (index > 0) {
179          // Cmid does have a value in the heap.
180          // (1) clear map[cmid].
181          // (2) shrink the heap by one slot.
182          //     (a) If index is the last element in the heap we have nothing
183          //         to do after we decrement nextIndex.
184          //     (b) If index is not the last element in the heap, then move the
185          //         last heap element to index and heapify.
186          map[cmid] = 0;
187          nextIndex--;
188          if (index < nextIndex) {
189            double oldValue = counts[index];
190            counts[index] = counts[nextIndex];
191            cmids[index] = cmids[nextIndex];
192            map[cmids[index]] = index;
193            if (counts[index] > oldValue) {
194              heapifyUp(index);
195            } else {
196              heapifyDown(index);
197            }
198          }
199        }
200        if (DEBUG) validityCheck();
201      }
202    
203      /**
204       * Augment the data associated with a given cmid by the specified number of samples
205       *
206       * @param cmid compiled method id
207       * @param addVal samples to add
208       */
209      public synchronized void augmentData(int cmid, double addVal) {
210        if (addVal == 0) return; // nothing to do
211        int index = findOrCreateHeapIdx(cmid);
212        counts[index] += addVal;
213        if (addVal > 0) {
214          heapifyUp(index);
215        } else {
216          heapifyDown(index);
217        }
218        if (DEBUG) validityCheck();
219      }
220    
221      /**
222       * Enqueue events describing the "hot" methods on the organizer's event queue.
223       *
224       * @param filterOptLevel filter out all methods already compiled at
225       *                       this opt level (or higher)
226       * @param threshold hotness value above which the method is considered
227       *                  to be hot. (0.0 to 1.0)
228       */
229      public synchronized void insertHotMethods(int filterOptLevel, double threshold) {
230        if (DEBUG) validityCheck();
231        insertHotMethodsInternal(1, filterOptLevel, hotnessToCounts(threshold));
232      }
233    
234      /**
235       * Collect the hot methods that have been compiled at the given opt level.
236       *
237       * @param optLevel  target opt level
238       * @param threshold hotness value above which the method is considered to
239       *                  be hot. (0.0 to 1.0)
240       * @return a MethodCountSet containing an
241       *            array of compiled methods and an array of their counts.
242       */
243      public synchronized MethodCountSet collectHotMethods(int optLevel, double threshold) {
244        if (DEBUG) validityCheck();
245        ArrayList<HotMethodRecompilationEvent> collect = new ArrayList<HotMethodRecompilationEvent>();
246        collectHotOptMethodsInternal(1, collect, hotnessToCounts(threshold), optLevel);
247    
248        // now package the data into the form the caller expects.
249        int numHotMethods = collect.size();
250        double[] numCounts = new double[numHotMethods];
251        CompiledMethod[] hotMethods = new CompiledMethod[numHotMethods];
252        for (int i = 0; i < numHotMethods; i++) {
253          HotMethodEvent event = collect.get(i);
254          hotMethods[i] = event.getCompiledMethod();
255          numCounts[i] = event.getNumSamples();
256        }
257        return new MethodCountSet(hotMethods, numCounts);
258      }
259    
260      /**
261       * Convert from a [0.0...1.0] hotness value to the number of counts
262       * that represents that fraction of hotness
263       *
264       * @param hotness a value [0.0...1.0]
265       * @return a number of counts
266       */
267      private double hotnessToCounts(double hotness) {
268        return totalCountsTaken * hotness;
269      }
270    
271      /**
272       * Convert a value to a [0.0...1.0] fractional hotness value
273       *
274       * @param numCounts number of counts
275       * @return a value [0.0...1.0]
276       */
277      private double countsToHotness(double numCounts) {
278        if (VM.VerifyAssertions) VM._assert(numCounts <= totalCountsTaken);
279        return numCounts / totalCountsTaken;
280      }
281    
282      /**
283       * Recursive implementation of insertHotMethods. Exploit heap property.
284       * Note threshold has been converted into a count value by my caller!
285       *
286       * @param index count array index
287       * @param filterOptLevel filter out all methods already compiled at
288       *                       this opt level (or higher)
289       * @param threshold hotness value above which the method is considered
290       *                  to be hot. (0.0 to 1.0)
291       */
292      private void insertHotMethodsInternal(int index, int filterOptLevel, double threshold) {
293        if (index < nextIndex) {
294          if (counts[index] > threshold) {
295            int cmid = cmids[index];
296            CompiledMethod cm = CompiledMethods.getCompiledMethod(cmid);
297            if (cm == null) {                       // obsolete and deleted
298              reset(cmid);                          // free up this slot
299              // Visit new one in the slot
300              insertHotMethodsInternal(index, filterOptLevel, threshold);
301            } else {
302              int compilerType = cm.getCompilerType();
303              // Enqueue it unless it's either a trap method or already
304              // opt compiled at filterOptLevel or higher.
305              if (!(compilerType == CompiledMethod.TRAP ||
306                    (compilerType == CompiledMethod.OPT &&
307                     (((OptCompiledMethod) cm).getOptLevel() >= filterOptLevel)))) {
308                double ns = counts[index];
309                HotMethodRecompilationEvent event = new HotMethodRecompilationEvent(cm, ns);
310                Controller.controllerInputQueue.insert(ns, event);
311                AOSLogging.logger.controllerNotifiedForHotness(cm, ns);
312              }
313    
314              // Since I was hot enough, also consider my children.
315              insertHotMethodsInternal(index * 2, filterOptLevel, threshold);
316              insertHotMethodsInternal(index * 2 + 1, filterOptLevel, threshold);
317            }
318          }
319        }
320      }
321    
322      /**
323       * Recursive implementation of collectHotOptNMethods.
324       * Exploit heap property.
325       * Constraint: threshold has been converted into a count value by my caller!
326       *
327       * @param index count array index
328       * @param collect vector used to collect output.
329       * @param threshold hotness value above which the method is considered
330       *                  to be hot. (0.0 to 1.0)
331       * @param optLevel target opt level to look for.
332       */
333      private void collectHotOptMethodsInternal(int index, List<HotMethodRecompilationEvent> collect, double threshold,
334                                                int optLevel) {
335        if (index < nextIndex) {
336          if (counts[index] > threshold) {
337            int cmid = cmids[index];
338            CompiledMethod cm = CompiledMethods.getCompiledMethod(cmid);
339            if (cm == null) {                       // obsolete and deleted
340              reset(cmid);                          // free up this slot
341              // Visit new one in the slot
342              collectHotOptMethodsInternal(index, collect, threshold, optLevel);
343            } else {
344              int compilerType = cm.getCompilerType();
345              if (compilerType == CompiledMethod.OPT && ((OptCompiledMethod) cm).getOptLevel() == optLevel) {
346                double ns = counts[index];
347                collect.add(new HotMethodRecompilationEvent(cm, ns));
348              }
349    
350              // Since I was hot enough, also consider my children.
351              collectHotOptMethodsInternal(index * 2, collect, threshold, optLevel);
352              collectHotOptMethodsInternal(index * 2 + 1, collect, threshold, optLevel);
353            }
354          }
355        }
356      }
357    
358      /**
359       * Either find the index that is already being used to hold the counts
360       * for cmid or allocate a new entry in the heap for cmid.
361       *
362       * @param cmid compiled method id
363       * @return count array index
364       */
365      private int findOrCreateHeapIdx(int cmid) {
366        if (cmid >= map.length) {
367          growHeapMap(cmid);
368        }
369        int index = map[cmid];
370        if (index == 0) {
371          // A new cmid. Allocate a heap entry for it.
372          index = nextIndex++;
373          if (index >= counts.length) {
374            growHeap();
375          }
376          counts[index] = 0.0;
377          cmids[index] = cmid;
378          map[cmid] = index;
379        }
380        return index;
381      }
382    
383      /**
384       * Find the index that is already being used to hold the counts for cmid.
385       * If no such index exists, return 0.
386       *
387       * @param cmid compiled method id
388       */
389      private int findHeapIdx(int cmid) {
390        if (cmid < map.length) {
391          int index = map[cmid];
392          return index;
393        } else {
394          return 0;
395        }
396      }
397    
398      /**
399       * Grow the map to be at least as large as would be required to map cmid
400       *
401       * @param cmid compiled method id
402       */
403      private void growHeapMap(int cmid) {
404        int[] newMap = new int[Math.max((int) (map.length * 1.25), cmid + 1)];
405        for (int j = 0; j < map.length; j++) {
406          newMap[j] = map[j];
407        }
408        map = newMap;
409      }
410    
411      /**
412       * Increase the size of the count's backing arrays
413       */
414      private void growHeap() {
415        double[] tmp1 = new double[counts.length * 2];
416        for (int i = 1; i < counts.length; i++) {
417          tmp1[i] = counts[i];
418        }
419        counts = tmp1;
420        int[] tmp2 = new int[cmids.length * 2];
421        for (int i = 1; i < cmids.length; i++) {
422          tmp2[i] = cmids[i];
423        }
424        cmids = tmp2;
425      }
426    
427      /**
428       * Restore the heap property after increasing a count array entry's value
429       *
430       * @param index of count array entry
431       */
432      private void heapifyUp(int index) {
433        int current = index;
434        int parent = index / 2;
435        while (parent > 0 && counts[parent] < counts[current]) {
436          swap(parent, current);
437          current = parent;
438          parent = parent / 2;
439        }
440      }
441    
442      /**
443       * Restore the heap property after decreasing a count array entry's value
444       *
445       * @param index of count array entry
446       */
447      private void heapifyDown(int index) {
448        int current = index;
449        int child1 = current * 2;
450        while (child1 < nextIndex) {
451          int child2 = current * 2 + 1;
452          int larger = (child2 < nextIndex && counts[child2] > counts[child1]) ? child2 : child1;
453          if (counts[current] >= counts[larger]) break; // done
454          swap(current, larger);
455          current = larger;
456          child1 = current * 2;
457        }
458      }
459    
460      /**
461       * Swap the heap entries at i and j.
462       *
463       * @param i count array index
464       * @param j count array index
465       */
466      private void swap(int i, int j) {
467        double tmpS = counts[i];
468        counts[i] = counts[j];
469        counts[j] = tmpS;
470        int tmpC = cmids[i];
471        cmids[i] = cmids[j];
472        cmids[j] = tmpC;
473        map[cmids[i]] = i;
474        map[cmids[j]] = j;
475      }
476    
477      /**
478       * Validate that internal fields are consistent.
479       * This is very expensive.  Only use for debugging purposes.
480       */
481      private void validityCheck() {
482        if (DEBUG && VM.VerifyAssertions) {
483          // (1) Verify map and cmids are in synch
484          for (int i = 0; i < map.length; i++) {
485            VM._assert(map[i] == 0 || cmids[map[i]] == i);
486          }
487          for (int i = 1; i < nextIndex; i++) {
488            VM._assert(map[cmids[i]] == i);
489          }
490    
491          // Verify that heap property holds on data.
492          for (int i = 2; i < nextIndex; i++) {
493            VM._assert(counts[i] <= counts[i / 2]);
494          }
495        }
496      }
497    }
498