普通文本  |  912行  |  29.96 KB

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "profiler.h"

#include <sys/file.h>
#include <sys/stat.h>
#include <sys/uio.h>

#include <fstream>

#include "art_method-inl.h"
#include "base/stl_util.h"
#include "base/time_utils.h"
#include "base/unix_file/fd_file.h"
#include "class_linker.h"
#include "common_throws.h"
#include "debugger.h"
#include "dex_file-inl.h"
#include "instrumentation.h"
#include "mirror/class-inl.h"
#include "mirror/dex_cache.h"
#include "mirror/object_array-inl.h"
#include "mirror/object-inl.h"
#include "os.h"
#include "scoped_thread_state_change.h"
#include "ScopedLocalRef.h"
#include "thread.h"
#include "thread_list.h"
#include "utils.h"

#include "entrypoints/quick/quick_entrypoints.h"

namespace art {

BackgroundMethodSamplingProfiler* BackgroundMethodSamplingProfiler::profiler_ = nullptr;
pthread_t BackgroundMethodSamplingProfiler::profiler_pthread_ = 0U;
volatile bool BackgroundMethodSamplingProfiler::shutting_down_ = false;

// TODO: this profiler runs regardless of the state of the machine.  Maybe we should use the
// wakelock or something to modify the run characteristics.  This can be done when we
// have some performance data after it's been used for a while.

// Walk through the method within depth of max_depth_ on the Java stack
class BoundedStackVisitor : public StackVisitor {
 public:
  BoundedStackVisitor(std::vector<std::pair<ArtMethod*, uint32_t>>* stack,
      Thread* thread, uint32_t max_depth)
      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
      : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
        stack_(stack),
        max_depth_(max_depth),
        depth_(0) {}

  bool VisitFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
    ArtMethod* m = GetMethod();
    if (m->IsRuntimeMethod()) {
      return true;
    }
    uint32_t dex_pc_ = GetDexPc();
    stack_->push_back(std::make_pair(m, dex_pc_));
    ++depth_;
    if (depth_ < max_depth_) {
      return true;
    } else {
      return false;
    }
  }

 private:
  std::vector<std::pair<ArtMethod*, uint32_t>>* stack_;
  const uint32_t max_depth_;
  uint32_t depth_;
};

// This is called from either a thread list traversal or from a checkpoint.  Regardless
// of which caller, the mutator lock must be held.
static void GetSample(Thread* thread, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  BackgroundMethodSamplingProfiler* profiler =
      reinterpret_cast<BackgroundMethodSamplingProfiler*>(arg);
  const ProfilerOptions profile_options = profiler->GetProfilerOptions();
  switch (profile_options.GetProfileType()) {
    case kProfilerMethod: {
      ArtMethod* method = thread->GetCurrentMethod(nullptr);
      if ((false) && method == nullptr) {
        LOG(INFO) << "No current method available";
        std::ostringstream os;
        thread->Dump(os);
        std::string data(os.str());
        LOG(INFO) << data;
      }
      profiler->RecordMethod(method);
      break;
    }
    case kProfilerBoundedStack: {
      std::vector<InstructionLocation> stack;
      uint32_t max_depth = profile_options.GetMaxStackDepth();
      BoundedStackVisitor bounded_stack_visitor(&stack, thread, max_depth);
      bounded_stack_visitor.WalkStack();
      profiler->RecordStack(stack);
      break;
    }
    default:
      LOG(INFO) << "This profile type is not implemented.";
  }
}

// A closure that is called by the thread checkpoint code.
class SampleCheckpoint FINAL : public Closure {
 public:
  explicit SampleCheckpoint(BackgroundMethodSamplingProfiler* const profiler) :
    profiler_(profiler) {}

  void Run(Thread* thread) OVERRIDE {
    Thread* self = Thread::Current();
    if (thread == nullptr) {
      LOG(ERROR) << "Checkpoint with nullptr thread";
      return;
    }

    // Grab the mutator lock (shared access).
    ScopedObjectAccess soa(self);

    // Grab a sample.
    GetSample(thread, this->profiler_);

    // And finally tell the barrier that we're done.
    this->profiler_->GetBarrier().Pass(self);
  }

 private:
  BackgroundMethodSamplingProfiler* const profiler_;
};

bool BackgroundMethodSamplingProfiler::ShuttingDown(Thread* self) {
  MutexLock mu(self, *Locks::profiler_lock_);
  return shutting_down_;
}

void* BackgroundMethodSamplingProfiler::RunProfilerThread(void* arg) {
  Runtime* runtime = Runtime::Current();
  BackgroundMethodSamplingProfiler* profiler =
      reinterpret_cast<BackgroundMethodSamplingProfiler*>(arg);

  // Add a random delay for the first time run so that we don't hammer the CPU
  // with all profiles running at the same time.
  const int kRandomDelayMaxSecs = 30;
  const double kMaxBackoffSecs = 24*60*60;   // Max backoff time.

  srand(MicroTime() * getpid());
  int startup_delay = rand() % kRandomDelayMaxSecs;   // random delay for startup.


  CHECK(runtime->AttachCurrentThread("Profiler", true, runtime->GetSystemThreadGroup(),
                                      !runtime->IsAotCompiler()));

  Thread* self = Thread::Current();

  double backoff = 1.0;
  while (true) {
    if (ShuttingDown(self)) {
      break;
    }

    {
      // wait until we need to run another profile
      uint64_t delay_secs = profiler->options_.GetPeriodS() * backoff;

      // Add a startup delay to prevent all the profiles running at once.
      delay_secs += startup_delay;

      // Immediate startup for benchmarking?
      if (profiler->options_.GetStartImmediately() && startup_delay > 0) {
        delay_secs = 0;
      }

      startup_delay = 0;

      VLOG(profiler) << "Delaying profile start for " << delay_secs << " secs";
      MutexLock mu(self, profiler->wait_lock_);
      profiler->period_condition_.TimedWait(self, delay_secs * 1000, 0);
      // We were either signaled by Stop or timedout, in either case ignore the timed out result.

      // Expand the backoff by its coefficient, but don't go beyond the max.
      backoff = std::min(backoff * profiler->options_.GetBackoffCoefficient(), kMaxBackoffSecs);
    }

    if (ShuttingDown(self)) {
      break;
    }


    uint64_t start_us = MicroTime();
    uint64_t end_us = start_us + profiler->options_.GetDurationS() * UINT64_C(1000000);
    uint64_t now_us = start_us;

    VLOG(profiler) << "Starting profiling run now for "
                   << PrettyDuration((end_us - start_us) * 1000);

    SampleCheckpoint check_point(profiler);

    size_t valid_samples = 0;
    while (now_us < end_us) {
      if (ShuttingDown(self)) {
        break;
      }

      usleep(profiler->options_.GetIntervalUs());    // Non-interruptible sleep.

      ThreadList* thread_list = runtime->GetThreadList();

      profiler->profiler_barrier_->Init(self, 0);
      size_t barrier_count = thread_list->RunCheckpointOnRunnableThreads(&check_point);

      // All threads are suspended, nothing to do.
      if (barrier_count == 0) {
        now_us = MicroTime();
        continue;
      }

      valid_samples += barrier_count;

      ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);

      // Wait for the barrier to be crossed by all runnable threads.  This wait
      // is done with a timeout so that we can detect problems with the checkpoint
      // running code.  We should never see this.
      const uint32_t kWaitTimeoutMs = 10000;

      // Wait for all threads to pass the barrier.
      bool timed_out =  profiler->profiler_barrier_->Increment(self, barrier_count, kWaitTimeoutMs);

      // We should never get a timeout.  If we do, it suggests a problem with the checkpoint
      // code.  Crash the process in this case.
      CHECK(!timed_out);

      // Update the current time.
      now_us = MicroTime();
    }

    if (valid_samples > 0) {
      // After the profile has been taken, write it out.
      ScopedObjectAccess soa(self);   // Acquire the mutator lock.
      uint32_t size = profiler->WriteProfile();
      VLOG(profiler) << "Profile size: " << size;
    }
  }

  LOG(INFO) << "Profiler shutdown";
  runtime->DetachCurrentThread();
  return nullptr;
}

// Write out the profile file if we are generating a profile.
uint32_t BackgroundMethodSamplingProfiler::WriteProfile() {
  std::string full_name = output_filename_;
  VLOG(profiler) << "Saving profile to " << full_name;

  int fd = open(full_name.c_str(), O_RDWR);
  if (fd < 0) {
    // Open failed.
    LOG(ERROR) << "Failed to open profile file " << full_name;
    return 0;
  }

  // Lock the file for exclusive access.  This will block if another process is using
  // the file.
  int err = flock(fd, LOCK_EX);
  if (err < 0) {
    LOG(ERROR) << "Failed to lock profile file " << full_name;
    return 0;
  }

  // Read the previous profile.
  profile_table_.ReadPrevious(fd, options_.GetProfileType());

  // Move back to the start of the file.
  lseek(fd, 0, SEEK_SET);

  // Format the profile output and write to the file.
  std::ostringstream os;
  uint32_t num_methods = DumpProfile(os);
  std::string data(os.str());
  const char *p = data.c_str();
  size_t length = data.length();
  size_t full_length = length;
  do {
    int n = ::write(fd, p, length);
    p += n;
    length -= n;
  } while (length > 0);

  // Truncate the file to the new length.
  ftruncate(fd, full_length);

  // Now unlock the file, allowing another process in.
  err = flock(fd, LOCK_UN);
  if (err < 0) {
    LOG(ERROR) << "Failed to unlock profile file " << full_name;
  }

  // Done, close the file.
  ::close(fd);

  // Clean the profile for the next time.
  CleanProfile();

  return num_methods;
}

bool BackgroundMethodSamplingProfiler::Start(
    const std::string& output_filename, const ProfilerOptions& options) {
  if (!options.IsEnabled()) {
    return false;
  }

  CHECK(!output_filename.empty());

  Thread* self = Thread::Current();
  {
    MutexLock mu(self, *Locks::profiler_lock_);
    // Don't start two profiler threads.
    if (profiler_ != nullptr) {
      return true;
    }
  }

  LOG(INFO) << "Starting profiler using output file: " << output_filename
            << " and options: " << options;
  {
    MutexLock mu(self, *Locks::profiler_lock_);
    profiler_ = new BackgroundMethodSamplingProfiler(output_filename, options);

    CHECK_PTHREAD_CALL(pthread_create, (&profiler_pthread_, nullptr, &RunProfilerThread,
        reinterpret_cast<void*>(profiler_)),
                       "Profiler thread");
  }
  return true;
}



void BackgroundMethodSamplingProfiler::Stop() {
  BackgroundMethodSamplingProfiler* profiler = nullptr;
  pthread_t profiler_pthread = 0U;
  {
    MutexLock trace_mu(Thread::Current(), *Locks::profiler_lock_);
    CHECK(!shutting_down_);
    profiler = profiler_;
    shutting_down_ = true;
    profiler_pthread = profiler_pthread_;
  }

  // Now wake up the sampler thread if it sleeping.
  {
    MutexLock profile_mu(Thread::Current(), profiler->wait_lock_);
    profiler->period_condition_.Signal(Thread::Current());
  }
  // Wait for the sample thread to stop.
  CHECK_PTHREAD_CALL(pthread_join, (profiler_pthread, nullptr), "profiler thread shutdown");

  {
    MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
    profiler_ = nullptr;
  }
  delete profiler;
}


void BackgroundMethodSamplingProfiler::Shutdown() {
  Stop();
}

BackgroundMethodSamplingProfiler::BackgroundMethodSamplingProfiler(
  const std::string& output_filename, const ProfilerOptions& options)
    : output_filename_(output_filename),
      options_(options),
      wait_lock_("Profile wait lock"),
      period_condition_("Profile condition", wait_lock_),
      profile_table_(wait_lock_),
      profiler_barrier_(new Barrier(0)) {
  // Populate the filtered_methods set.
  // This is empty right now, but to add a method, do this:
  //
  // filtered_methods_.insert("void java.lang.Object.wait(long, int)");
}

// Filter out methods the profiler doesn't want to record.
// We require mutator lock since some statistics will be updated here.
bool BackgroundMethodSamplingProfiler::ProcessMethod(ArtMethod* method) {
  if (method == nullptr) {
    profile_table_.NullMethod();
    // Don't record a null method.
    return false;
  }

  mirror::Class* cls = method->GetDeclaringClass();
  if (cls != nullptr) {
    if (cls->GetClassLoader() == nullptr) {
      // Don't include things in the boot
      profile_table_.BootMethod();
      return false;
    }
  }

  bool is_filtered = false;

  if (strcmp(method->GetName(), "<clinit>") == 0) {
    // always filter out class init
    is_filtered = true;
  }

  // Filter out methods by name if there are any.
  if (!is_filtered && filtered_methods_.size() > 0) {
    std::string method_full_name = PrettyMethod(method);

    // Don't include specific filtered methods.
    is_filtered = filtered_methods_.count(method_full_name) != 0;
  }
  return !is_filtered;
}

// A method has been hit, record its invocation in the method map.
// The mutator_lock must be held (shared) when this is called.
void BackgroundMethodSamplingProfiler::RecordMethod(ArtMethod* method) {
  // Add to the profile table unless it is filtered out.
  if (ProcessMethod(method)) {
    profile_table_.Put(method);
  }
}

// Record the current bounded stack into sampling results.
void BackgroundMethodSamplingProfiler::RecordStack(const std::vector<InstructionLocation>& stack) {
  if (stack.size() == 0) {
    return;
  }
  // Get the method on top of the stack. We use this method to perform filtering.
  ArtMethod* method = stack.front().first;
  if (ProcessMethod(method)) {
      profile_table_.PutStack(stack);
  }
}

// Clean out any recordings for the method traces.
void BackgroundMethodSamplingProfiler::CleanProfile() {
  profile_table_.Clear();
}

uint32_t BackgroundMethodSamplingProfiler::DumpProfile(std::ostream& os) {
  return profile_table_.Write(os, options_.GetProfileType());
}

// Profile Table.
// This holds a mapping of ArtMethod* to a count of how many times a sample
// hit it at the top of the stack.
ProfileSampleResults::ProfileSampleResults(Mutex& lock) : lock_(lock), num_samples_(0),
    num_null_methods_(0),
    num_boot_methods_(0) {
  for (int i = 0; i < kHashSize; i++) {
    table[i] = nullptr;
  }
  method_context_table = nullptr;
  stack_trie_root_ = nullptr;
}

ProfileSampleResults::~ProfileSampleResults() {
  Clear();
}

// Add a method to the profile table.  If it's the first time the method
// has been seen, add it with count=1, otherwise increment the count.
void ProfileSampleResults::Put(ArtMethod* method) {
  MutexLock mu(Thread::Current(), lock_);
  uint32_t index = Hash(method);
  if (table[index] == nullptr) {
    table[index] = new Map();
  }
  Map::iterator i = table[index]->find(method);
  if (i == table[index]->end()) {
    (*table[index])[method] = 1;
  } else {
    i->second++;
  }
  num_samples_++;
}

// Add a bounded stack to the profile table. Only the count of the method on
// top of the frame will be increased.
void ProfileSampleResults::PutStack(const std::vector<InstructionLocation>& stack) {
  MutexLock mu(Thread::Current(), lock_);
  ScopedObjectAccess soa(Thread::Current());
  if (stack_trie_root_ == nullptr) {
    // The root of the stack trie is a dummy node so that we don't have to maintain
    // a collection of tries.
    stack_trie_root_ = new StackTrieNode();
  }

  StackTrieNode* current = stack_trie_root_;
  if (stack.size() == 0) {
    current->IncreaseCount();
    return;
  }

  for (std::vector<InstructionLocation>::const_reverse_iterator iter = stack.rbegin();
       iter != stack.rend(); ++iter) {
    InstructionLocation inst_loc = *iter;
    ArtMethod* method = inst_loc.first;
    if (method == nullptr) {
      // skip null method
      continue;
    }
    uint32_t dex_pc = inst_loc.second;
    uint32_t method_idx = method->GetDexMethodIndex();
    const DexFile* dex_file = method->GetDeclaringClass()->GetDexCache()->GetDexFile();
    MethodReference method_ref(dex_file, method_idx);
    StackTrieNode* child = current->FindChild(method_ref, dex_pc);
    if (child != nullptr) {
      current = child;
    } else {
      uint32_t method_size = 0;
      const DexFile::CodeItem* codeitem = method->GetCodeItem();
      if (codeitem != nullptr) {
        method_size = codeitem->insns_size_in_code_units_;
      }
      StackTrieNode* new_node = new StackTrieNode(method_ref, dex_pc, method_size, current);
      current->AppendChild(new_node);
      current = new_node;
    }
  }

  if (current != stack_trie_root_ && current->GetCount() == 0) {
    // Insert into method_context table;
    if (method_context_table == nullptr) {
      method_context_table = new MethodContextMap();
    }
    MethodReference method = current->GetMethod();
    MethodContextMap::iterator i = method_context_table->find(method);
    if (i == method_context_table->end()) {
      TrieNodeSet* node_set = new TrieNodeSet();
      node_set->insert(current);
      (*method_context_table)[method] = node_set;
    } else {
      TrieNodeSet* node_set = i->second;
      node_set->insert(current);
    }
  }
  current->IncreaseCount();
  num_samples_++;
}

// Write the profile table to the output stream.  Also merge with the previous profile.
uint32_t ProfileSampleResults::Write(std::ostream& os, ProfileDataType type) {
  ScopedObjectAccess soa(Thread::Current());
  num_samples_ += previous_num_samples_;
  num_null_methods_ += previous_num_null_methods_;
  num_boot_methods_ += previous_num_boot_methods_;

  VLOG(profiler) << "Profile: "
                 << num_samples_ << "/" << num_null_methods_ << "/" << num_boot_methods_;
  os << num_samples_ << "/" << num_null_methods_ << "/" << num_boot_methods_ << "\n";
  uint32_t num_methods = 0;
  if (type == kProfilerMethod) {
    for (int i = 0 ; i < kHashSize; i++) {
      Map *map = table[i];
      if (map != nullptr) {
        for (const auto &meth_iter : *map) {
          ArtMethod *method = meth_iter.first;
          std::string method_name = PrettyMethod(method);

          const DexFile::CodeItem* codeitem = method->GetCodeItem();
          uint32_t method_size = 0;
          if (codeitem != nullptr) {
            method_size = codeitem->insns_size_in_code_units_;
          }
          uint32_t count = meth_iter.second;

          // Merge this profile entry with one from a previous run (if present).  Also
          // remove the previous entry.
          PreviousProfile::iterator pi = previous_.find(method_name);
          if (pi != previous_.end()) {
            count += pi->second.count_;
            previous_.erase(pi);
          }
          os << StringPrintf("%s/%u/%u\n",  method_name.c_str(), count, method_size);
          ++num_methods;
        }
      }
    }
  } else if (type == kProfilerBoundedStack) {
    if (method_context_table != nullptr) {
      for (const auto &method_iter : *method_context_table) {
        MethodReference method = method_iter.first;
        TrieNodeSet* node_set = method_iter.second;
        std::string method_name = PrettyMethod(method.dex_method_index, *(method.dex_file));
        uint32_t method_size = 0;
        uint32_t total_count = 0;
        PreviousContextMap new_context_map;
        for (const auto &trie_node_i : *node_set) {
          StackTrieNode* node = trie_node_i;
          method_size = node->GetMethodSize();
          uint32_t count = node->GetCount();
          uint32_t dexpc = node->GetDexPC();
          total_count += count;

          StackTrieNode* current = node->GetParent();
          // We go backward on the trie to retrieve context and dex_pc until the dummy root.
          // The format of the context is "method_1@pc_1@method_2@pc_2@..."
          std::vector<std::string> context_vector;
          while (current != nullptr && current->GetParent() != nullptr) {
            context_vector.push_back(StringPrintf("%s@%u",
                PrettyMethod(current->GetMethod().dex_method_index, *(current->GetMethod().dex_file)).c_str(),
                current->GetDexPC()));
            current = current->GetParent();
          }
          std::string context_sig = Join(context_vector, '@');
          new_context_map[std::make_pair(dexpc, context_sig)] = count;
        }

        PreviousProfile::iterator pi = previous_.find(method_name);
        if (pi != previous_.end()) {
          total_count += pi->second.count_;
          PreviousContextMap* previous_context_map = pi->second.context_map_;
          if (previous_context_map != nullptr) {
            for (const auto &context_i : *previous_context_map) {
              uint32_t count = context_i.second;
              PreviousContextMap::iterator ci = new_context_map.find(context_i.first);
              if (ci == new_context_map.end()) {
                new_context_map[context_i.first] = count;
              } else {
                ci->second += count;
              }
            }
          }
          delete previous_context_map;
          previous_.erase(pi);
        }
        // We write out profile data with dex pc and context information in the following format:
        // "method/total_count/size/[pc_1:count_1:context_1#pc_2:count_2:context_2#...]".
        std::vector<std::string> context_count_vector;
        for (const auto &context_i : new_context_map) {
          context_count_vector.push_back(StringPrintf("%u:%u:%s", context_i.first.first,
              context_i.second, context_i.first.second.c_str()));
        }
        os << StringPrintf("%s/%u/%u/[%s]\n", method_name.c_str(), total_count,
            method_size, Join(context_count_vector, '#').c_str());
        ++num_methods;
      }
    }
  }

  // Now we write out the remaining previous methods.
  for (const auto &pi : previous_) {
    if (type == kProfilerMethod) {
      os << StringPrintf("%s/%u/%u\n",  pi.first.c_str(), pi.second.count_, pi.second.method_size_);
    } else if (type == kProfilerBoundedStack) {
      os << StringPrintf("%s/%u/%u/[",  pi.first.c_str(), pi.second.count_, pi.second.method_size_);
      PreviousContextMap* previous_context_map = pi.second.context_map_;
      if (previous_context_map != nullptr) {
        std::vector<std::string> context_count_vector;
        for (const auto &context_i : *previous_context_map) {
          context_count_vector.push_back(StringPrintf("%u:%u:%s", context_i.first.first,
              context_i.second, context_i.first.second.c_str()));
        }
        os << Join(context_count_vector, '#');
      }
      os << "]\n";
    }
    ++num_methods;
  }
  return num_methods;
}

void ProfileSampleResults::Clear() {
  num_samples_ = 0;
  num_null_methods_ = 0;
  num_boot_methods_ = 0;
  for (int i = 0; i < kHashSize; i++) {
    delete table[i];
    table[i] = nullptr;
  }
  if (stack_trie_root_ != nullptr) {
    stack_trie_root_->DeleteChildren();
    delete stack_trie_root_;
    stack_trie_root_ = nullptr;
    if (method_context_table != nullptr) {
      delete method_context_table;
      method_context_table = nullptr;
    }
  }
  for (auto &pi : previous_) {
    if (pi.second.context_map_ != nullptr) {
      delete pi.second.context_map_;
      pi.second.context_map_ = nullptr;
    }
  }
  previous_.clear();
}

uint32_t ProfileSampleResults::Hash(ArtMethod* method) {
  return (PointerToLowMemUInt32(method) >> 3) % kHashSize;
}

// Read a single line into the given string.  Returns true if everything OK, false
// on EOF or error.
static bool ReadProfileLine(int fd, std::string& line) {
  char buf[4];
  line.clear();
  while (true) {
    int n = read(fd, buf, 1);     // TODO: could speed this up but is it worth it?
    if (n != 1) {
      return false;
    }
    if (buf[0] == '\n') {
      break;
    }
    line += buf[0];
  }
  return true;
}

void ProfileSampleResults::ReadPrevious(int fd, ProfileDataType type) {
  // Reset counters.
  previous_num_samples_ = previous_num_null_methods_ = previous_num_boot_methods_ = 0;

  std::string line;

  // The first line contains summary information.
  if (!ReadProfileLine(fd, line)) {
    return;
  }
  std::vector<std::string> summary_info;
  Split(line, '/', &summary_info);
  if (summary_info.size() != 3) {
    // Bad summary info.  It should be count/nullcount/bootcount
    return;
  }
  previous_num_samples_ = strtoul(summary_info[0].c_str(), nullptr, 10);
  previous_num_null_methods_ = strtoul(summary_info[1].c_str(), nullptr, 10);
  previous_num_boot_methods_ = strtoul(summary_info[2].c_str(), nullptr, 10);

  // Now read each line until the end of file.  Each line consists of 3 or 4 fields separated by /
  while (true) {
    if (!ReadProfileLine(fd, line)) {
      break;
    }
    std::vector<std::string> info;
    Split(line, '/', &info);
    if (info.size() != 3 && info.size() != 4) {
      // Malformed.
      break;
    }
    std::string methodname = info[0];
    uint32_t total_count = strtoul(info[1].c_str(), nullptr, 10);
    uint32_t size = strtoul(info[2].c_str(), nullptr, 10);
    PreviousContextMap* context_map = nullptr;
    if (type == kProfilerBoundedStack && info.size() == 4) {
      context_map = new PreviousContextMap();
      std::string context_counts_str = info[3].substr(1, info[3].size() - 2);
      std::vector<std::string> context_count_pairs;
      Split(context_counts_str, '#', &context_count_pairs);
      for (uint32_t i = 0; i < context_count_pairs.size(); ++i) {
        std::vector<std::string> context_count;
        Split(context_count_pairs[i], ':', &context_count);
        if (context_count.size() == 2) {
          // Handles the situtation when the profile file doesn't contain context information.
          uint32_t dexpc = strtoul(context_count[0].c_str(), nullptr, 10);
          uint32_t count = strtoul(context_count[1].c_str(), nullptr, 10);
          (*context_map)[std::make_pair(dexpc, "")] = count;
        } else {
          // Handles the situtation when the profile file contains context information.
          uint32_t dexpc = strtoul(context_count[0].c_str(), nullptr, 10);
          uint32_t count = strtoul(context_count[1].c_str(), nullptr, 10);
          std::string context = context_count[2];
          (*context_map)[std::make_pair(dexpc, context)] = count;
        }
      }
    }
    previous_[methodname] = PreviousValue(total_count, size, context_map);
  }
}

bool ProfileFile::LoadFile(const std::string& fileName) {
  LOG(VERBOSE) << "reading profile file " << fileName;
  struct stat st;
  int err = stat(fileName.c_str(), &st);
  if (err == -1) {
    LOG(VERBOSE) << "not found";
    return false;
  }
  if (st.st_size == 0) {
    return false;  // Empty profiles are invalid.
  }
  std::ifstream in(fileName.c_str());
  if (!in) {
    LOG(VERBOSE) << "profile file " << fileName << " exists but can't be opened";
    LOG(VERBOSE) << "file owner: " << st.st_uid << ":" << st.st_gid;
    LOG(VERBOSE) << "me: " << getuid() << ":" << getgid();
    LOG(VERBOSE) << "file permissions: " << std::oct << st.st_mode;
    LOG(VERBOSE) << "errno: " << errno;
    return false;
  }
  // The first line contains summary information.
  std::string line;
  std::getline(in, line);
  if (in.eof()) {
    return false;
  }
  std::vector<std::string> summary_info;
  Split(line, '/', &summary_info);
  if (summary_info.size() != 3) {
    // Bad summary info.  It should be total/null/boot.
    return false;
  }
  // This is the number of hits in all profiled methods (without null or boot methods)
  uint32_t total_count = strtoul(summary_info[0].c_str(), nullptr, 10);

  // Now read each line until the end of file.  Each line consists of 3 fields separated by '/'.
  // Store the info in descending order given by the most used methods.
  typedef std::set<std::pair<int, std::vector<std::string>>> ProfileSet;
  ProfileSet countSet;
  while (!in.eof()) {
    std::getline(in, line);
    if (in.eof()) {
      break;
    }
    std::vector<std::string> info;
    Split(line, '/', &info);
    if (info.size() != 3 && info.size() != 4) {
      // Malformed.
      return false;
    }
    int count = atoi(info[1].c_str());
    countSet.insert(std::make_pair(-count, info));
  }

  uint32_t curTotalCount = 0;
  ProfileSet::iterator end = countSet.end();
  const ProfileData* prevData = nullptr;
  for (ProfileSet::iterator it = countSet.begin(); it != end ; it++) {
    const std::string& methodname = it->second[0];
    uint32_t count = -it->first;
    uint32_t size = strtoul(it->second[2].c_str(), nullptr, 10);
    double usedPercent = (count * 100.0) / total_count;

    curTotalCount += count;
    // Methods with the same count should be part of the same top K percentage bucket.
    double topKPercentage = (prevData != nullptr) && (prevData->GetCount() == count)
      ? prevData->GetTopKUsedPercentage()
      : 100 * static_cast<double>(curTotalCount) / static_cast<double>(total_count);

    // Add it to the profile map.
    ProfileData curData = ProfileData(methodname, count, size, usedPercent, topKPercentage);
    profile_map_[methodname] = curData;
    prevData = &curData;
  }
  return true;
}

bool ProfileFile::GetProfileData(ProfileFile::ProfileData* data, const std::string& method_name) {
  ProfileMap::iterator i = profile_map_.find(method_name);
  if (i == profile_map_.end()) {
    return false;
  }
  *data = i->second;
  return true;
}

bool ProfileFile::GetTopKSamples(std::set<std::string>& topKSamples, double topKPercentage) {
  ProfileMap::iterator end = profile_map_.end();
  for (ProfileMap::iterator it = profile_map_.begin(); it != end; it++) {
    if (it->second.GetTopKUsedPercentage() < topKPercentage) {
      topKSamples.insert(it->first);
    }
  }
  return true;
}

StackTrieNode* StackTrieNode::FindChild(MethodReference method, uint32_t dex_pc) {
  if (children_.size() == 0) {
    return nullptr;
  }
  // Create a dummy node for searching.
  StackTrieNode* node = new StackTrieNode(method, dex_pc, 0, nullptr);
  std::set<StackTrieNode*, StackTrieNodeComparator>::iterator i = children_.find(node);
  delete node;
  return (i == children_.end()) ? nullptr : *i;
}

void StackTrieNode::DeleteChildren() {
  for (auto &child : children_) {
    if (child != nullptr) {
      child->DeleteChildren();
      delete child;
    }
  }
}

}  // namespace art