| Index: runtime/vm/timeline.cc | 
| diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc | 
| index d55a8dcd9b6ff949ade135d668c4213168268963..1a8536a8c0d5c39832ed1da5c48e5078e336cf64 100644 | 
| --- a/runtime/vm/timeline.cc | 
| +++ b/runtime/vm/timeline.cc | 
| @@ -479,6 +479,36 @@ IsolateTimelineEventFilter::IsolateTimelineEventFilter(Isolate* isolate) | 
| } | 
|  | 
|  | 
| +DartTimelineEvent::DartTimelineEvent() | 
| +    : isolate_(NULL), | 
| +      event_as_json_(NULL) { | 
| +} | 
| + | 
| + | 
| +DartTimelineEvent::~DartTimelineEvent() { | 
| +  Clear(); | 
| +} | 
| + | 
| + | 
| +void DartTimelineEvent::Clear() { | 
| +  if (isolate_ != NULL) { | 
| +    isolate_ = NULL; | 
| +  } | 
| +  if (event_as_json_ != NULL) { | 
| +    free(event_as_json_); | 
| +    event_as_json_ = NULL; | 
| +  } | 
| +} | 
| + | 
| + | 
| +void DartTimelineEvent::Init(Isolate* isolate, const char* event) { | 
| +  ASSERT(isolate_ == NULL); | 
| +  ASSERT(event != NULL); | 
| +  isolate_ = isolate; | 
| +  event_as_json_ = strdup(event); | 
| +} | 
| + | 
| + | 
| TimelineEventRecorder::TimelineEventRecorder() | 
| : global_block_(NULL), | 
| async_id_(0) { | 
| @@ -550,6 +580,21 @@ TimelineEvent* TimelineEventRecorder::GlobalBlockStartEvent() { | 
| } | 
|  | 
|  | 
| +// Trims the ']' character. | 
| +static void TrimOutput(char* output, | 
| +                       intptr_t* output_length) { | 
| +  ASSERT(output != NULL); | 
| +  ASSERT(output_length != NULL); | 
| +  ASSERT(*output_length >= 2); | 
| +  // We expect the first character to be the opening of an array. | 
| +  ASSERT(output[0] == '['); | 
| +  // We expect the last character to be the closing of an array. | 
| +  ASSERT(output[*output_length - 1] == ']'); | 
| +  // Skip the ]. | 
| +  *output_length -= 1; | 
| +} | 
| + | 
| + | 
| void TimelineEventRecorder::WriteTo(const char* directory) { | 
| Dart_FileOpenCallback file_open = Isolate::file_open_callback(); | 
| Dart_FileWriteCallback file_write = Isolate::file_write_callback(); | 
| @@ -557,13 +602,11 @@ void TimelineEventRecorder::WriteTo(const char* directory) { | 
| if ((file_open == NULL) || (file_write == NULL) || (file_close == NULL)) { | 
| return; | 
| } | 
| +  Thread* T = Thread::Current(); | 
| +  StackZone zone(T); | 
|  | 
| Timeline::ReclaimAllBlocks(); | 
|  | 
| -  JSONStream js; | 
| -  TimelineEventFilter filter; | 
| -  PrintJSON(&js, &filter); | 
| - | 
| intptr_t pid = OS::ProcessId(); | 
| char* filename = OS::SCreate(NULL, | 
| "%s/dart-timeline-%" Pd ".json", directory, pid); | 
| @@ -574,8 +617,43 @@ void TimelineEventRecorder::WriteTo(const char* directory) { | 
| return; | 
| } | 
| free(filename); | 
| -  (*file_write)(js.buffer()->buf(), js.buffer()->length(), file); | 
| + | 
| +  JSONStream js; | 
| +  TimelineEventFilter filter; | 
| +  PrintTraceEvent(&js, &filter); | 
| +  // Steal output from JSONStream. | 
| +  char* output = NULL; | 
| +  intptr_t output_length = 0; | 
| +  js.Steal(const_cast<const char**>(&output), &output_length); | 
| +  TrimOutput(output, &output_length); | 
| +  ASSERT(output_length >= 1); | 
| +  (*file_write)(output, output_length, file); | 
| +  // Free the stolen output. | 
| +  free(output); | 
| + | 
| +  const char* dart_events = | 
| +      DartTimelineEventIterator::PrintTraceEvents(this, | 
| +                                                  zone.GetZone(), | 
| +                                                  NULL); | 
| + | 
| +  // If we wrote out vm events and have dart events, write out the comma. | 
| +  if ((output_length > 1) && (dart_events != NULL)) { | 
| +    // Write out the ',' character. | 
| +    const char* comma = ","; | 
| +    (*file_write)(comma, 1, file); | 
| +  } | 
| + | 
| +  // Write out the Dart events. | 
| +  if (dart_events != NULL) { | 
| +    (*file_write)(dart_events, strlen(dart_events), file); | 
| +  } | 
| + | 
| +  // Write out the ']' character. | 
| +  const char* array_close = "]"; | 
| +  (*file_write)(array_close, 1, file); | 
| (*file_close)(file); | 
| + | 
| +  return; | 
| } | 
|  | 
|  | 
| @@ -615,7 +693,10 @@ TimelineEventRingRecorder::TimelineEventRingRecorder(intptr_t capacity) | 
| : blocks_(NULL), | 
| capacity_(capacity), | 
| num_blocks_(0), | 
| -      block_cursor_(0) { | 
| +      block_cursor_(0), | 
| +      dart_events_(NULL), | 
| +      dart_events_capacity_(capacity), | 
| +      dart_events_cursor_(0) { | 
| // Capacity must be a multiple of TimelineEventBlock::kBlockSize | 
| ASSERT((capacity % TimelineEventBlock::kBlockSize) == 0); | 
| // Allocate blocks array. | 
| @@ -631,6 +712,13 @@ TimelineEventRingRecorder::TimelineEventRingRecorder(intptr_t capacity) | 
| for (intptr_t i = 0; i < num_blocks_ - 1; i++) { | 
| blocks_[i]->set_next(blocks_[i + 1]); | 
| } | 
| +  // Pre-allocate DartTimelineEvents. | 
| +  dart_events_ = | 
| +      reinterpret_cast<DartTimelineEvent**>( | 
| +          calloc(dart_events_capacity_, sizeof(DartTimelineEvent*))); | 
| +  for (intptr_t i = 0; i < dart_events_capacity_; i++) { | 
| +    dart_events_[i] = new DartTimelineEvent(); | 
| +  } | 
| } | 
|  | 
|  | 
| @@ -641,6 +729,12 @@ TimelineEventRingRecorder::~TimelineEventRingRecorder() { | 
| delete block; | 
| } | 
| free(blocks_); | 
| +  // Delete all DartTimelineEvents. | 
| +  for (intptr_t i = 0; i < dart_events_capacity_; i++) { | 
| +    DartTimelineEvent* event = dart_events_[i]; | 
| +    delete event; | 
| +  } | 
| +  free(dart_events_); | 
| } | 
|  | 
|  | 
| @@ -681,6 +775,33 @@ void TimelineEventRingRecorder::PrintJSON(JSONStream* js, | 
| } | 
|  | 
|  | 
| +void TimelineEventRingRecorder::AppendDartEvent(Isolate* isolate, | 
| +                                                const char* event) { | 
| +  MutexLocker ml(&lock_); | 
| +  // TODO(johnmccutchan): If locking becomes an issue, use the Isolate to store | 
| +  // the events. | 
| +  if (dart_events_cursor_ == dart_events_capacity_) { | 
| +    dart_events_cursor_ = 0; | 
| +  } | 
| +  ASSERT(dart_events_[dart_events_cursor_] != NULL); | 
| +  dart_events_[dart_events_cursor_]->Clear(); | 
| +  dart_events_[dart_events_cursor_]->Init(isolate, event); | 
| +  dart_events_cursor_++; | 
| +} | 
| + | 
| + | 
| +intptr_t TimelineEventRingRecorder::NumDartEventsLocked() { | 
| +  return dart_events_capacity_; | 
| +} | 
| + | 
| + | 
| +DartTimelineEvent* TimelineEventRingRecorder::DartEventAtLocked(intptr_t i) { | 
| +  ASSERT(i >= 0); | 
| +  ASSERT(i < dart_events_capacity_); | 
| +  return dart_events_[i]; | 
| +} | 
| + | 
| + | 
| void TimelineEventRingRecorder::PrintTraceEvent(JSONStream* js, | 
| TimelineEventFilter* filter) { | 
| JSONArray events(js); | 
| @@ -761,6 +882,25 @@ void TimelineEventStreamingRecorder::PrintTraceEvent( | 
| } | 
|  | 
|  | 
| +void TimelineEventStreamingRecorder::AppendDartEvent(Isolate* isolate, | 
| +                                                     const char* event) { | 
| +  if (event != NULL) { | 
| +    StreamDartEvent(event); | 
| +  } | 
| +} | 
| + | 
| + | 
| +intptr_t TimelineEventStreamingRecorder::NumDartEventsLocked() { | 
| +  return 0; | 
| +} | 
| + | 
| + | 
| +DartTimelineEvent* TimelineEventStreamingRecorder::DartEventAtLocked( | 
| +    intptr_t i) { | 
| +  return NULL; | 
| +} | 
| + | 
| + | 
| TimelineEvent* TimelineEventStreamingRecorder::StartEvent() { | 
| TimelineEvent* event = new TimelineEvent(); | 
| return event; | 
| @@ -775,7 +915,10 @@ void TimelineEventStreamingRecorder::CompleteEvent(TimelineEvent* event) { | 
|  | 
| TimelineEventEndlessRecorder::TimelineEventEndlessRecorder() | 
| : head_(NULL), | 
| -      block_index_(0) { | 
| +      block_index_(0), | 
| +      dart_events_(NULL), | 
| +      dart_events_capacity_(0), | 
| +      dart_events_cursor_(0) { | 
| } | 
|  | 
|  | 
| @@ -800,6 +943,31 @@ void TimelineEventEndlessRecorder::PrintTraceEvent( | 
| } | 
|  | 
|  | 
| +void TimelineEventEndlessRecorder::AppendDartEvent(Isolate* isolate, | 
| +                                                   const char* event) { | 
| +  MutexLocker ml(&lock_); | 
| +  // TODO(johnmccutchan): If locking becomes an issue, use the Isolate to store | 
| +  // the events. | 
| +  if (dart_events_cursor_ == dart_events_capacity_) { | 
| +    // Grow. | 
| +    intptr_t new_capacity = | 
| +        (dart_events_capacity_ == 0) ? 16 : dart_events_capacity_ * 2; | 
| +    dart_events_ = reinterpret_cast<DartTimelineEvent**>( | 
| +        realloc(dart_events_, new_capacity * sizeof(DartTimelineEvent*))); | 
| +    for (intptr_t i = dart_events_capacity_; i < new_capacity; i++) { | 
| +      // Fill with NULLs. | 
| +      dart_events_[i] = NULL; | 
| +    } | 
| +    dart_events_capacity_ = new_capacity; | 
| +  } | 
| +  ASSERT(dart_events_cursor_ < dart_events_capacity_); | 
| +  DartTimelineEvent* dart_event = new DartTimelineEvent(); | 
| +  dart_event->Init(isolate, event); | 
| +  ASSERT(dart_events_[dart_events_cursor_] == NULL); | 
| +  dart_events_[dart_events_cursor_++] = dart_event; | 
| +} | 
| + | 
| + | 
| TimelineEventBlock* TimelineEventEndlessRecorder::GetHeadBlockLocked() { | 
| return head_; | 
| } | 
| @@ -833,6 +1001,19 @@ TimelineEventBlock* TimelineEventEndlessRecorder::GetNewBlockLocked( | 
| } | 
|  | 
|  | 
| +intptr_t TimelineEventEndlessRecorder::NumDartEventsLocked() { | 
| +  return dart_events_cursor_; | 
| +} | 
| + | 
| + | 
| +DartTimelineEvent* TimelineEventEndlessRecorder::DartEventAtLocked( | 
| +    intptr_t i) { | 
| +  ASSERT(i >= 0); | 
| +  ASSERT(i < dart_events_cursor_); | 
| +  return dart_events_[i]; | 
| +} | 
| + | 
| + | 
| void TimelineEventEndlessRecorder::PrintJSONEvents( | 
| JSONArray* events, | 
| TimelineEventFilter* filter) const { | 
| @@ -1000,4 +1181,80 @@ TimelineEventBlock* TimelineEventBlockIterator::Next() { | 
| return r; | 
| } | 
|  | 
| + | 
| +DartTimelineEventIterator::DartTimelineEventIterator( | 
| +    TimelineEventRecorder* recorder) | 
| +    : cursor_(0), | 
| +      num_events_(0), | 
| +      recorder_(NULL) { | 
| +  Reset(recorder); | 
| +} | 
| + | 
| + | 
| +DartTimelineEventIterator::~DartTimelineEventIterator() { | 
| +  Reset(NULL); | 
| +} | 
| + | 
| + | 
| +void DartTimelineEventIterator::Reset(TimelineEventRecorder* recorder) { | 
| +  // Clear state. | 
| +  cursor_ = 0; | 
| +  num_events_ = 0; | 
| +  if (recorder_ != NULL) { | 
| +    // Unlock old recorder. | 
| +    recorder_->lock_.Unlock(); | 
| +  } | 
| +  recorder_ = recorder; | 
| +  if (recorder_ == NULL) { | 
| +    return; | 
| +  } | 
| +  // Lock new recorder. | 
| +  recorder_->lock_.Lock(); | 
| +  cursor_ = 0; | 
| +  num_events_ = recorder_->NumDartEventsLocked(); | 
| +} | 
| + | 
| + | 
| +bool DartTimelineEventIterator::HasNext() const { | 
| +  return cursor_ < num_events_; | 
| +} | 
| + | 
| + | 
| +DartTimelineEvent* DartTimelineEventIterator::Next() { | 
| +  ASSERT(cursor_ < num_events_); | 
| +  DartTimelineEvent* r = recorder_->DartEventAtLocked(cursor_); | 
| +  cursor_++; | 
| +  return r; | 
| +} | 
| + | 
| +const char* DartTimelineEventIterator::PrintTraceEvents( | 
| +    TimelineEventRecorder* recorder, | 
| +    Zone* zone, | 
| +    Isolate* isolate) { | 
| +  if (recorder == NULL) { | 
| +    return NULL; | 
| +  } | 
| + | 
| +  if (zone == NULL) { | 
| +    return NULL; | 
| +  } | 
| + | 
| +  char* result = NULL; | 
| +  DartTimelineEventIterator iterator(recorder); | 
| +  while (iterator.HasNext()) { | 
| +    DartTimelineEvent* event = iterator.Next(); | 
| +    if (!event->IsValid()) { | 
| +      // Skip invalid | 
| +      continue; | 
| +    } | 
| +    if ((isolate != NULL) && (isolate != event->isolate())) { | 
| +      // If an isolate was specified, skip events from other isolates. | 
| +      continue; | 
| +    } | 
| +    ASSERT(event->event_as_json() != NULL); | 
| +    result = zone->ConcatStrings(result, event->event_as_json()); | 
| +  } | 
| +  return result; | 
| +} | 
| + | 
| }  // namespace dart | 
|  |