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    
014    package org.jikesrvm.tuningfork;
015    
016    import java.io.File;
017    import java.io.FileNotFoundException;
018    import java.io.FileOutputStream;
019    import java.io.IOException;
020    import java.io.OutputStream;
021    
022    import org.jikesrvm.VM;
023    import org.jikesrvm.Callbacks;
024    import org.jikesrvm.Configuration;
025    import org.jikesrvm.Options;
026    import org.jikesrvm.Callbacks.ExitMonitor;
027    import org.jikesrvm.scheduler.RVMThread;
028    import org.jikesrvm.util.HashSetRVM;
029    import org.vmmagic.pragma.Uninterruptible;
030    
031    import com.ibm.tuningfork.tracegen.chunk.EventChunk;
032    import com.ibm.tuningfork.tracegen.chunk.EventTypeChunk;
033    import com.ibm.tuningfork.tracegen.chunk.EventTypeSpaceChunk;
034    import com.ibm.tuningfork.tracegen.chunk.FeedHeaderChunk;
035    import com.ibm.tuningfork.tracegen.chunk.FeedletChunk;
036    import com.ibm.tuningfork.tracegen.chunk.PropertyTableChunk;
037    import com.ibm.tuningfork.tracegen.chunk.RawChunk;
038    import com.ibm.tuningfork.tracegen.types.EventAttribute;
039    import com.ibm.tuningfork.tracegen.types.EventType;
040    import 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     */
046    public 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 & 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       * Define 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       */
132      public EventType defineEvent(String name, String description) {
133        if (state == State.SHUT_DOWN) return null;
134        EventType result = new EventType(name, description);
135        internalDefineEvent(result);
136        return result;
137      }
138    
139      /**
140       * Define an EventType
141       * @param name The name to give the event
142       * @param description A human readable description of the event for display in the TuningFork UI.
143       * @param attribute Description of the event's single data value
144       */
145      public EventType defineEvent(String name, String description, EventAttribute attribute) {
146        if (state == State.SHUT_DOWN) return null;
147        EventType result = new EventType(name, description, attribute);
148        internalDefineEvent(result);
149        return result;
150      }
151    
152      /**
153       * Define an EventType
154       * @param name The name to give the event
155       * @param description A human readable description of the event for display in the TuningFork UI.
156       * @param attributes Descriptions of the event's data values
157       */
158      public EventType defineEvent(String name, String description, EventAttribute[] attributes) {
159        if (state == State.SHUT_DOWN) return null;
160        EventType result = new EventType(name, description, attributes);
161        internalDefineEvent(result);
162        return result;
163      }
164    
165      private synchronized void internalDefineEvent(EventType et) {
166        if (!activeEventTypeChunk.add(et)) {
167          activeEventTypeChunk.close();
168          unwrittenMetaChunks.enqueue(activeEventTypeChunk);
169          activeEventTypeChunk = new EventTypeChunk();
170          if (!activeEventTypeChunk.add(et)) {
171            if (VM.VerifyAssertions) {
172              VM.sysFail("EventTypeChunk is too small to to add event type "+et);
173            }
174          }
175        }
176      }
177    
178      /*
179       * Support for Properties
180       */
181    
182      /**
183       * Add a Property (key, value) pair to the Feed.
184       * @param key the key for the property
185       * @param value the value for the property
186       */
187      public synchronized void addProperty(String key, String value) {
188        if (state == State.SHUT_DOWN) return;
189        if (!activePropertyTableChunk.add(key, value)) {
190          activePropertyTableChunk.close();
191          unwrittenMetaChunks.enqueue(activePropertyTableChunk);
192          activePropertyTableChunk = new PropertyTableChunk();
193          if (!activePropertyTableChunk.add(key, value)) {
194            if (VM.VerifyAssertions) {
195              VM.sysFail("PropertyTableChunk is too small to to add "+key+" = " +value);
196            }
197          }
198        }
199      }
200    
201      /*
202       * Support for Feedlets
203       */
204      public synchronized Feedlet makeFeedlet(String name, String description) {
205        Feedlet f = new Feedlet(this, nextFeedletId++);
206        if (state == State.SHUT_DOWN) {
207          f.enabled = false;
208          return f;
209        }
210        if (!activeFeedletChunk.add(f.getFeedletIndex(), name, description)) {
211          activeFeedletChunk.close();
212          unwrittenMetaChunks.enqueue(activeFeedletChunk);
213          activeFeedletChunk = new FeedletChunk();
214          if (!activeFeedletChunk.add(f.getFeedletIndex(), name, description)) {
215            if (VM.VerifyAssertions) {
216              VM.sysFail("FeedletChunk is too small to to add feedlet "+name+" (" +description+")");
217            }
218          }
219        }
220    
221        activeFeedlets.add(f);
222    
223        /* TODO: if we have less than 2 event chunks per active feedlet, then we should
224         *       allocate more here!
225         *       NOTE: We must ensure they are externally kept alive (see comment in EventChunkQueue).
226         */
227        return f;
228      }
229    
230      public synchronized void removeFeedlet(Feedlet feedlet) {
231        if (activeFeedlets.contains(feedlet)) {
232          activeFeedlets.remove(feedlet);
233          shutdownFeedlet(feedlet);
234        }
235      }
236    
237      private synchronized void shutdownAllFeedlets() {
238        for (Feedlet f : activeFeedlets) {
239          shutdownFeedlet(f);
240        }
241        activeFeedlets.removeAll();
242      }
243    
244    
245      private void shutdownFeedlet(Feedlet feedlet) {
246        feedlet.shutdown();
247        if (!activeFeedletChunk.remove(feedlet.getFeedletIndex())) {
248          activeFeedletChunk.close();
249          unwrittenMetaChunks.enqueue(activeFeedletChunk);
250          activeFeedletChunk = new FeedletChunk();
251          if (!activeFeedletChunk.remove(feedlet.getFeedletIndex())) {
252            if (VM.VerifyAssertions) {
253              VM.sysFail("Unable to do single remove operation on a new feedlet chunk");
254            }
255          }
256        }
257      }
258    
259      /*
260       * Daemon Threads & I/O
261       */
262    
263      private void createDaemonThreads() {
264        /* Create primary I/O thread */
265        Thread ioThread = new Thread(new Runnable() {
266          @Override
267          public void run() {
268            ioThreadMainLoop();
269          }}, "TuningFork Primary I/O thread");
270        ioThread.setDaemon(true);
271        ioThread.start();
272    
273        /* Install shutdown hook that will delay VM exit until I/O completes. */
274        Callbacks.addExitMonitor(new ExitMonitor(){
275          @Override
276          public void notifyExit(int value) {
277            state = State.SHUTTING_DOWN;
278            while (state == State.SHUTTING_DOWN) {
279              try {
280                Thread.sleep(1);
281              } catch (InterruptedException e) {
282              }
283            }
284          }});
285      }
286    
287      private void ioThreadMainLoop() {
288        state = State.RUNNING_FILE;
289        while (true) {
290          try {
291            Thread.sleep(IO_INTERVAL_MS);
292          } catch (InterruptedException e) {
293            // Do nothing.
294          }
295          boolean shouldShutDown = state == State.SHUTTING_DOWN;
296          synchronized(this) {
297            if (shouldShutDown) {
298              shutdownAllFeedlets();
299            }
300          }
301          writeMetaChunks();
302          writeEventChunks();
303          if (shouldShutDown) {
304            state = State.SHUT_DOWN;
305            return;
306          }
307        }
308      }
309    
310      private synchronized void writeMetaChunks() {
311        try {
312          while (!unwrittenMetaChunks.isEmpty()) {
313            RawChunk c = unwrittenMetaChunks.dequeue();
314            c.write(outputStream);
315          }
316          if (activeEventTypeChunk != null && activeEventTypeChunk.hasData()) {
317            activeEventTypeChunk.close();
318            activeEventTypeChunk.write(outputStream);
319            activeEventTypeChunk.reset();
320          }
321          if (activeFeedletChunk != null && activeFeedletChunk.hasData()) {
322            activeFeedletChunk.close();
323            activeFeedletChunk.write(outputStream);
324            activeFeedletChunk.reset();
325          }
326          if (activePropertyTableChunk != null && activePropertyTableChunk.hasData()) {
327            activePropertyTableChunk.close();
328            activePropertyTableChunk.write(outputStream);
329            activePropertyTableChunk.reset();
330          }
331        } catch (IOException e) {
332          VM.sysWriteln("Exception while outputing trace TuningFork trace file");
333          e.printStackTrace();
334        }
335      }
336    
337      private synchronized void writeEventChunks() {
338        while (!unwrittenEventChunks.isEmpty()) {
339          EventChunk c = unwrittenEventChunks.dequeue();
340          try {
341            c.write(outputStream);
342          } catch (IOException e) {
343            VM.sysWriteln("Exception while outputing trace TuningFork trace file");
344            e.printStackTrace();
345          }
346          availableEventChunks.enqueue(c); /* reduce; reuse; recycle...*/
347        }
348      }
349    
350      @Uninterruptible
351      EventChunk getEventChunk() {
352        return availableEventChunks.dequeue();
353      }
354    
355      @Uninterruptible
356      public void returnFullEventChunk(EventChunk events) {
357        unwrittenEventChunks.enqueue(events);
358      }
359    
360    }