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 */
013package org.jikesrvm.adaptive.database.methodsamples;
014
015import java.util.ArrayList;
016import java.util.List;
017
018import org.jikesrvm.VM;
019import org.jikesrvm.adaptive.controller.Controller;
020import org.jikesrvm.adaptive.controller.HotMethodEvent;
021import org.jikesrvm.adaptive.controller.HotMethodRecompilationEvent;
022import org.jikesrvm.adaptive.measurements.Reportable;
023import org.jikesrvm.adaptive.util.AOSLogging;
024import org.jikesrvm.classloader.RVMMethod;
025import org.jikesrvm.compilers.common.CompiledMethod;
026import org.jikesrvm.compilers.common.CompiledMethods;
027import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
028import org.jikesrvm.scheduler.RVMThread;
029
030/**
031 * A container for recording how often a method is executed.
032 */
033public 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   * @param cmid compiled method id
159   * @return the current count for a given compiled method id.
160   */
161  public synchronized double getData(int cmid) {
162    int index = findHeapIdx(cmid);
163    if (index > 0) {
164      return counts[index];
165    } else {
166      return 0.0;
167    }
168  }
169
170  /**
171   * Reset (set to 0.0) the count for a given compiled method id.
172   *
173   * @param cmid compiled method id
174   */
175  public synchronized void reset(int cmid) {
176    int index = findHeapIdx(cmid);
177    if (index > 0) {
178      // Cmid does have a value in the heap.
179      // (1) clear map[cmid].
180      // (2) shrink the heap by one slot.
181      //     (a) If index is the last element in the heap we have nothing
182      //         to do after we decrement nextIndex.
183      //     (b) If index is not the last element in the heap, then move the
184      //         last heap element to index and heapify.
185      map[cmid] = 0;
186      nextIndex--;
187      if (index < nextIndex) {
188        double oldValue = counts[index];
189        counts[index] = counts[nextIndex];
190        cmids[index] = cmids[nextIndex];
191        map[cmids[index]] = index;
192        if (counts[index] > oldValue) {
193          heapifyUp(index);
194        } else {
195          heapifyDown(index);
196        }
197      }
198    }
199    if (DEBUG) validityCheck();
200  }
201
202  /**
203   * Augment the data associated with a given cmid by the specified number of samples
204   *
205   * @param cmid compiled method id
206   * @param addVal samples to add
207   */
208  public synchronized void augmentData(int cmid, double addVal) {
209    if (addVal == 0) return; // nothing to do
210    int index = findOrCreateHeapIdx(cmid);
211    counts[index] += addVal;
212    if (addVal > 0) {
213      heapifyUp(index);
214    } else {
215      heapifyDown(index);
216    }
217    if (DEBUG) validityCheck();
218  }
219
220  /**
221   * Enqueue events describing the "hot" methods on the organizer's event queue.
222   *
223   * @param filterOptLevel filter out all methods already compiled at
224   *                       this opt level (or higher)
225   * @param threshold hotness value above which the method is considered
226   *                  to be hot. (0.0 to 1.0)
227   */
228  public synchronized void insertHotMethods(int filterOptLevel, double threshold) {
229    if (DEBUG) validityCheck();
230    insertHotMethodsInternal(1, filterOptLevel, hotnessToCounts(threshold));
231  }
232
233  /**
234   * Collect the hot methods that have been compiled at the given opt level.
235   *
236   * @param optLevel  target opt level
237   * @param threshold hotness value above which the method is considered to
238   *                  be hot. (0.0 to 1.0)
239   * @return a MethodCountSet containing an
240   *            array of compiled methods and an array of their counts.
241   */
242  public synchronized MethodCountSet collectHotMethods(int optLevel, double threshold) {
243    if (DEBUG) validityCheck();
244    ArrayList<HotMethodRecompilationEvent> collect = new ArrayList<HotMethodRecompilationEvent>();
245    collectHotOptMethodsInternal(1, collect, hotnessToCounts(threshold), optLevel);
246
247    // now package the data into the form the caller expects.
248    int numHotMethods = collect.size();
249    double[] numCounts = new double[numHotMethods];
250    CompiledMethod[] hotMethods = new CompiledMethod[numHotMethods];
251    for (int i = 0; i < numHotMethods; i++) {
252      HotMethodEvent event = collect.get(i);
253      hotMethods[i] = event.getCompiledMethod();
254      numCounts[i] = event.getNumSamples();
255    }
256    return new MethodCountSet(hotMethods, numCounts);
257  }
258
259  /**
260   * Convert from a [0.0...1.0] hotness value to the number of counts
261   * that represents that fraction of hotness
262   *
263   * @param hotness a value [0.0...1.0]
264   * @return a number of counts
265   */
266  private double hotnessToCounts(double hotness) {
267    return totalCountsTaken * hotness;
268  }
269
270  /**
271   * Convert a value to a [0.0...1.0] fractional hotness value
272   *
273   * @param numCounts number of counts
274   * @return a value [0.0...1.0]
275   */
276  private double countsToHotness(double numCounts) {
277    if (VM.VerifyAssertions) VM._assert(numCounts <= totalCountsTaken);
278    return numCounts / totalCountsTaken;
279  }
280
281  /**
282   * Recursive implementation of insertHotMethods. Exploit heap property.
283   * Note threshold has been converted into a count value by my caller!
284   *
285   * @param index count array index
286   * @param filterOptLevel filter out all methods already compiled at
287   *                       this opt level (or higher)
288   * @param threshold hotness value above which the method is considered
289   *                  to be hot. (0.0 to 1.0)
290   */
291  private void insertHotMethodsInternal(int index, int filterOptLevel, double threshold) {
292    if (index < nextIndex) {
293      if (counts[index] > threshold) {
294        int cmid = cmids[index];
295        CompiledMethod cm = CompiledMethods.getCompiledMethod(cmid);
296        if (cm == null) {                       // obsolete and deleted
297          reset(cmid);                          // free up this slot
298          // Visit new one in the slot
299          insertHotMethodsInternal(index, filterOptLevel, threshold);
300        } else {
301          int compilerType = cm.getCompilerType();
302          // Enqueue it unless it's either a trap method or already
303          // opt compiled at filterOptLevel or higher.
304          if (!(compilerType == CompiledMethod.TRAP ||
305                (compilerType == CompiledMethod.OPT &&
306                 (((OptCompiledMethod) cm).getOptLevel() >= filterOptLevel)))) {
307            double ns = counts[index];
308            HotMethodRecompilationEvent event = new HotMethodRecompilationEvent(cm, ns);
309            Controller.controllerInputQueue.insert(ns, event);
310            AOSLogging.logger.controllerNotifiedForHotness(cm, ns);
311          }
312
313          // Since I was hot enough, also consider my children.
314          insertHotMethodsInternal(index * 2, filterOptLevel, threshold);
315          insertHotMethodsInternal(index * 2 + 1, filterOptLevel, threshold);
316        }
317      }
318    }
319  }
320
321  /**
322   * Recursive implementation of collectHotOptNMethods.
323   * Exploit heap property.
324   * Constraint: threshold has been converted into a count value by my caller!
325   *
326   * @param index count array index
327   * @param collect vector used to collect output.
328   * @param threshold hotness value above which the method is considered
329   *                  to be hot. (0.0 to 1.0)
330   * @param optLevel target opt level to look for.
331   */
332  private void collectHotOptMethodsInternal(int index, List<HotMethodRecompilationEvent> collect, double threshold,
333                                            int optLevel) {
334    if (index < nextIndex) {
335      if (counts[index] > threshold) {
336        int cmid = cmids[index];
337        CompiledMethod cm = CompiledMethods.getCompiledMethod(cmid);
338        if (cm == null) {                       // obsolete and deleted
339          reset(cmid);                          // free up this slot
340          // Visit new one in the slot
341          collectHotOptMethodsInternal(index, collect, threshold, optLevel);
342        } else {
343          int compilerType = cm.getCompilerType();
344          if (compilerType == CompiledMethod.OPT && ((OptCompiledMethod) cm).getOptLevel() == optLevel) {
345            double ns = counts[index];
346            collect.add(new HotMethodRecompilationEvent(cm, ns));
347          }
348
349          // Since I was hot enough, also consider my children.
350          collectHotOptMethodsInternal(index * 2, collect, threshold, optLevel);
351          collectHotOptMethodsInternal(index * 2 + 1, collect, threshold, optLevel);
352        }
353      }
354    }
355  }
356
357  /**
358   * Either find the index that is already being used to hold the counts
359   * for cmid or allocate a new entry in the heap for cmid.
360   *
361   * @param cmid compiled method id
362   * @return count array index
363   */
364  private int findOrCreateHeapIdx(int cmid) {
365    if (cmid >= map.length) {
366      growHeapMap(cmid);
367    }
368    int index = map[cmid];
369    if (index == 0) {
370      // A new cmid. Allocate a heap entry for it.
371      index = nextIndex++;
372      if (index >= counts.length) {
373        growHeap();
374      }
375      counts[index] = 0.0;
376      cmids[index] = cmid;
377      map[cmid] = index;
378    }
379    return index;
380  }
381
382  /**
383   * Finds the index that is already being used to hold the counts for cmid.
384   *
385   * @param cmid compiled method id
386   * @return 0 if no index exists, the index otherwise
387   */
388  private int findHeapIdx(int cmid) {
389    if (cmid < map.length) {
390      int index = map[cmid];
391      return index;
392    } else {
393      return 0;
394    }
395  }
396
397  /**
398   * Grow the map to be at least as large as would be required to map cmid
399   *
400   * @param cmid compiled method id
401   */
402  private void growHeapMap(int cmid) {
403    int[] newMap = new int[Math.max((int) (map.length * 1.25), cmid + 1)];
404    for (int j = 0; j < map.length; j++) {
405      newMap[j] = map[j];
406    }
407    map = newMap;
408  }
409
410  /**
411   * Increase the size of the count's backing arrays
412   */
413  private void growHeap() {
414    double[] tmp1 = new double[counts.length * 2];
415    for (int i = 1; i < counts.length; i++) {
416      tmp1[i] = counts[i];
417    }
418    counts = tmp1;
419    int[] tmp2 = new int[cmids.length * 2];
420    for (int i = 1; i < cmids.length; i++) {
421      tmp2[i] = cmids[i];
422    }
423    cmids = tmp2;
424  }
425
426  /**
427   * Restore the heap property after increasing a count array entry's value
428   *
429   * @param index of count array entry
430   */
431  private void heapifyUp(int index) {
432    int current = index;
433    int parent = index / 2;
434    while (parent > 0 && counts[parent] < counts[current]) {
435      swap(parent, current);
436      current = parent;
437      parent = parent / 2;
438    }
439  }
440
441  /**
442   * Restore the heap property after decreasing a count array entry's value
443   *
444   * @param index of count array entry
445   */
446  private void heapifyDown(int index) {
447    int current = index;
448    int child1 = current * 2;
449    while (child1 < nextIndex) {
450      int child2 = current * 2 + 1;
451      int larger = (child2 < nextIndex && counts[child2] > counts[child1]) ? child2 : child1;
452      if (counts[current] >= counts[larger]) break; // done
453      swap(current, larger);
454      current = larger;
455      child1 = current * 2;
456    }
457  }
458
459  /**
460   * Swap the heap entries at i and j.
461   *
462   * @param i count array index
463   * @param j count array index
464   */
465  private void swap(int i, int j) {
466    double tmpS = counts[i];
467    counts[i] = counts[j];
468    counts[j] = tmpS;
469    int tmpC = cmids[i];
470    cmids[i] = cmids[j];
471    cmids[j] = tmpC;
472    map[cmids[i]] = i;
473    map[cmids[j]] = j;
474  }
475
476  /**
477   * Validate that internal fields are consistent.
478   * This is very expensive.  Only use for debugging purposes.
479   */
480  private void validityCheck() {
481    if (DEBUG && VM.VerifyAssertions) {
482      // (1) Verify map and cmids are in synch
483      for (int i = 0; i < map.length; i++) {
484        VM._assert(map[i] == 0 || cmids[map[i]] == i);
485      }
486      for (int i = 1; i < nextIndex; i++) {
487        VM._assert(map[cmids[i]] == i);
488      }
489
490      // Verify that heap property holds on data.
491      for (int i = 2; i < nextIndex; i++) {
492        VM._assert(counts[i] <= counts[i / 2]);
493      }
494    }
495  }
496}
497