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
014package org.jikesrvm.tuningfork;
015
016import java.io.File;
017import java.io.FileNotFoundException;
018import java.io.FileOutputStream;
019import java.io.IOException;
020import java.io.OutputStream;
021
022import org.jikesrvm.VM;
023import org.jikesrvm.runtime.Callbacks;
024import org.jikesrvm.runtime.Callbacks.ExitMonitor;
025import org.jikesrvm.Configuration;
026import org.jikesrvm.Options;
027import org.jikesrvm.scheduler.RVMThread;
028import org.jikesrvm.util.HashSetRVM;
029import org.vmmagic.pragma.Uninterruptible;
030
031import com.ibm.tuningfork.tracegen.chunk.EventChunk;
032import com.ibm.tuningfork.tracegen.chunk.EventTypeChunk;
033import com.ibm.tuningfork.tracegen.chunk.EventTypeSpaceChunk;
034import com.ibm.tuningfork.tracegen.chunk.FeedHeaderChunk;
035import com.ibm.tuningfork.tracegen.chunk.FeedletChunk;
036import com.ibm.tuningfork.tracegen.chunk.PropertyTableChunk;
037import com.ibm.tuningfork.tracegen.chunk.RawChunk;
038import com.ibm.tuningfork.tracegen.types.EventAttribute;
039import com.ibm.tuningfork.tracegen.types.EventType;
040import com.ibm.tuningfork.tracegen.types.EventTypeSpaceVersion;
041
042/**
043 * TuningFork Trace Engine (roughly functionally equivalent to the
044 * Logger classes in the TuningFork JavaTraceGenerationLibrary).
045 */
046public final class TraceEngine {
047
048  public enum State { STARTING_UP, RUNNING_FILE, SHUTTING_DOWN, SHUT_DOWN };
049
050  public static final TraceEngine engine = new TraceEngine();
051  private static final int IO_INTERVAL_MS = 100;
052  private static final int INITIAL_EVENT_CHUNKS = 64;
053
054  private final ChunkQueue unwrittenMetaChunks = new ChunkQueue();
055  private final EventChunkQueue unwrittenEventChunks = new EventChunkQueue();
056  private final EventChunkQueue availableEventChunks = new EventChunkQueue();
057
058  private FeedletChunk activeFeedletChunk = new FeedletChunk();
059  private EventTypeChunk activeEventTypeChunk = new EventTypeChunk();
060  private PropertyTableChunk activePropertyTableChunk = new PropertyTableChunk();
061
062  private int nextFeedletId = 0;
063  private final HashSetRVM<Feedlet> activeFeedlets = new HashSetRVM<Feedlet>();
064
065  private OutputStream outputStream;
066  private State state = State.STARTING_UP;
067
068  private TraceEngine() {
069    /* Feed header and EventTypeSpaceChunk go first, so create & enqueue during bootimage writing */
070    unwrittenMetaChunks.enqueue(new FeedHeaderChunk());
071    unwrittenMetaChunks.enqueue(new EventTypeSpaceChunk(new EventTypeSpaceVersion("org.jikesrvm", 1)));
072
073    /* Pre-allocate all EventChunks into the bootimage so we can access them later via Untraced fields */
074    for (int i = 0; i < INITIAL_EVENT_CHUNKS; i++) {
075      availableEventChunks.enqueue(new EventChunk());
076    }
077  }
078
079
080  public void earlyStageBooting() {
081    if (Options.TuningForkTraceFile == null) {
082      /* tracing not enabled on this run, shut down engine to minimize overhead */
083      RVMThread.getCurrentFeedlet().enabled = false;
084      state = State.SHUT_DOWN;
085    } else {
086      unwrittenMetaChunks.enqueue(new SpaceDescriptorChunk());
087    }
088  }
089
090  public void fullyBootedVM() {
091    if (state != State.SHUT_DOWN) {
092      String traceFile = Options.TuningForkTraceFile;
093      if (!traceFile.endsWith(".trace")) {
094        traceFile = traceFile + ".trace";
095      }
096
097      File f = new File(traceFile);
098      try {
099        outputStream = new FileOutputStream(f);
100      } catch (FileNotFoundException e) {
101        VM.sysWriteln("Unable to open trace file " + f.getAbsolutePath());
102        VM.sysWriteln("continuing, but TuningFork trace generation is disabled.");
103        state = State.SHUT_DOWN;
104        return;
105      }
106
107      createDaemonThreads();
108      writeInitialProperites();
109    }
110  }
111
112
113  /**
114   * Put some basic properties about this VM build &amp; current execution into the feed.
115   */
116  private void writeInitialProperites() {
117    addProperty("rvm version", Configuration.RVM_VERSION_STRING);
118    addProperty("rvm config", Configuration.RVM_CONFIGURATION);
119    addProperty("Tick Frequency", "1000000000"); /* a tick is one nanosecond */
120  }
121
122
123  /*
124   * Support for defining EventTypes
125   */
126
127  /**
128   * Defines an EventType.
129   * @param name The name to give the event
130   * @param description A human readable description of the event for display in the TuningFork UI.
131   * @return the event type instance
132   */
133  public EventType defineEvent(String name, String description) {
134    if (state == State.SHUT_DOWN) return null;
135    EventType result = new EventType(name, description);
136    internalDefineEvent(result);
137    return result;
138  }
139
140  /**
141   * Define an EventType.
142   * @param name The name to give the event
143   * @param description A human readable description of the event for display in the TuningFork UI.
144   * @param attribute Description of the event's single data value
145   * @return the event type instance
146   */
147  public EventType defineEvent(String name, String description, EventAttribute attribute) {
148    if (state == State.SHUT_DOWN) return null;
149    EventType result = new EventType(name, description, attribute);
150    internalDefineEvent(result);
151    return result;
152  }
153
154  /**
155   * Defines an EventType.
156   * @param name The name to give the event
157   * @param description A human readable description of the event for display in the TuningFork UI.
158   * @param attributes Descriptions of the event's data values
159   * @return the event type instance
160   */
161  public EventType defineEvent(String name, String description, EventAttribute[] attributes) {
162    if (state == State.SHUT_DOWN) return null;
163    EventType result = new EventType(name, description, attributes);
164    internalDefineEvent(result);
165    return result;
166  }
167
168  private synchronized void internalDefineEvent(EventType et) {
169    if (!activeEventTypeChunk.add(et)) {
170      activeEventTypeChunk.close();
171      unwrittenMetaChunks.enqueue(activeEventTypeChunk);
172      activeEventTypeChunk = new EventTypeChunk();
173      if (!activeEventTypeChunk.add(et)) {
174        if (VM.VerifyAssertions) {
175          VM.sysFail("EventTypeChunk is too small to to add event type " + et);
176        }
177      }
178    }
179  }
180
181  /*
182   * Support for Properties
183   */
184
185  /**
186   * Add a Property (key, value) pair to the Feed.
187   * @param key the key for the property
188   * @param value the value for the property
189   */
190  public synchronized void addProperty(String key, String value) {
191    if (state == State.SHUT_DOWN) return;
192    if (!activePropertyTableChunk.add(key, value)) {
193      activePropertyTableChunk.close();
194      unwrittenMetaChunks.enqueue(activePropertyTableChunk);
195      activePropertyTableChunk = new PropertyTableChunk();
196      if (!activePropertyTableChunk.add(key, value)) {
197        if (VM.VerifyAssertions) {
198          VM.sysFail("PropertyTableChunk is too small to to add " + key + " = " + value);
199        }
200      }
201    }
202  }
203
204  /*
205   * Support for Feedlets
206   */
207  public synchronized Feedlet makeFeedlet(String name, String description) {
208    Feedlet f = new Feedlet(this, nextFeedletId++);
209    if (state == State.SHUT_DOWN) {
210      f.enabled = false;
211      return f;
212    }
213    if (!activeFeedletChunk.add(f.getFeedletIndex(), name, description)) {
214      activeFeedletChunk.close();
215      unwrittenMetaChunks.enqueue(activeFeedletChunk);
216      activeFeedletChunk = new FeedletChunk();
217      if (!activeFeedletChunk.add(f.getFeedletIndex(), name, description)) {
218        if (VM.VerifyAssertions) {
219          VM.sysFail("FeedletChunk is too small to to add feedlet " + name + " ("  + description + ")");
220        }
221      }
222    }
223
224    activeFeedlets.add(f);
225
226    /* TODO: if we have less than 2 event chunks per active feedlet, then we should
227     *       allocate more here!
228     *       NOTE: We must ensure they are externally kept alive (see comment in EventChunkQueue).
229     */
230    return f;
231  }
232
233  public synchronized void removeFeedlet(Feedlet feedlet) {
234    if (activeFeedlets.contains(feedlet)) {
235      activeFeedlets.remove(feedlet);
236      shutdownFeedlet(feedlet);
237    }
238  }
239
240  private synchronized void shutdownAllFeedlets() {
241    for (Feedlet f : activeFeedlets) {
242      shutdownFeedlet(f);
243    }
244    activeFeedlets.removeAll();
245  }
246
247
248  private void shutdownFeedlet(Feedlet feedlet) {
249    feedlet.shutdown();
250    if (!activeFeedletChunk.remove(feedlet.getFeedletIndex())) {
251      activeFeedletChunk.close();
252      unwrittenMetaChunks.enqueue(activeFeedletChunk);
253      activeFeedletChunk = new FeedletChunk();
254      if (!activeFeedletChunk.remove(feedlet.getFeedletIndex())) {
255        if (VM.VerifyAssertions) {
256          VM.sysFail("Unable to do single remove operation on a new feedlet chunk");
257        }
258      }
259    }
260  }
261
262  /*
263   * Daemon Threads & I/O
264   */
265
266  private void createDaemonThreads() {
267    /* Create primary I/O thread */
268    Thread ioThread = new Thread(new Runnable() {
269      @Override
270      public void run() {
271        ioThreadMainLoop();
272      }}, "TuningFork Primary I/O thread");
273    ioThread.setDaemon(true);
274    ioThread.start();
275
276    /* Install shutdown hook that will delay VM exit until I/O completes. */
277    Callbacks.addExitMonitor(new ExitMonitor(){
278      @Override
279      public void notifyExit(int value) {
280        state = State.SHUTTING_DOWN;
281        while (state == State.SHUTTING_DOWN) {
282          try {
283            Thread.sleep(1);
284          } catch (InterruptedException e) {
285          }
286        }
287      }});
288  }
289
290  private void ioThreadMainLoop() {
291    state = State.RUNNING_FILE;
292    while (true) {
293      try {
294        Thread.sleep(IO_INTERVAL_MS);
295      } catch (InterruptedException e) {
296        // Do nothing.
297      }
298      boolean shouldShutDown = state == State.SHUTTING_DOWN;
299      synchronized (this) {
300        if (shouldShutDown) {
301          shutdownAllFeedlets();
302        }
303      }
304      writeMetaChunks();
305      writeEventChunks();
306      if (shouldShutDown) {
307        state = State.SHUT_DOWN;
308        return;
309      }
310    }
311  }
312
313  private synchronized void writeMetaChunks() {
314    try {
315      while (!unwrittenMetaChunks.isEmpty()) {
316        RawChunk c = unwrittenMetaChunks.dequeue();
317        c.write(outputStream);
318      }
319      if (activeEventTypeChunk != null && activeEventTypeChunk.hasData()) {
320        activeEventTypeChunk.close();
321        activeEventTypeChunk.write(outputStream);
322        activeEventTypeChunk.reset();
323      }
324      if (activeFeedletChunk != null && activeFeedletChunk.hasData()) {
325        activeFeedletChunk.close();
326        activeFeedletChunk.write(outputStream);
327        activeFeedletChunk.reset();
328      }
329      if (activePropertyTableChunk != null && activePropertyTableChunk.hasData()) {
330        activePropertyTableChunk.close();
331        activePropertyTableChunk.write(outputStream);
332        activePropertyTableChunk.reset();
333      }
334    } catch (IOException e) {
335      VM.sysWriteln("Exception while outputing trace TuningFork trace file");
336      e.printStackTrace();
337    }
338  }
339
340  private synchronized void writeEventChunks() {
341    while (!unwrittenEventChunks.isEmpty()) {
342      EventChunk c = unwrittenEventChunks.dequeue();
343      try {
344        c.write(outputStream);
345      } catch (IOException e) {
346        VM.sysWriteln("Exception while outputing trace TuningFork trace file");
347        e.printStackTrace();
348      }
349      availableEventChunks.enqueue(c); /* reduce; reuse; recycle...*/
350    }
351  }
352
353  @Uninterruptible
354  EventChunk getEventChunk() {
355    return availableEventChunks.dequeue();
356  }
357
358  @Uninterruptible
359  public void returnFullEventChunk(EventChunk events) {
360    unwrittenEventChunks.enqueue(events);
361  }
362
363}