// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_DEBUG_ACTIVITY_ANALYZER_H_
#define BASE_DEBUG_ACTIVITY_ANALYZER_H_

#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>

#include "base/base_export.h"
#include "base/debug/activity_tracker.h"

namespace base {
namespace debug {

class GlobalActivityAnalyzer;

// This class provides analysis of data captured from a ThreadActivityTracker.
// When created, it takes a snapshot of the data held by the tracker and
// makes that information available to other code.
class BASE_EXPORT ThreadActivityAnalyzer {
 public:
  struct BASE_EXPORT Snapshot : ThreadActivityTracker::Snapshot {
    Snapshot();
    ~Snapshot();

    // The user-data snapshot for an activity, matching the |activity_stack|
    // of ThreadActivityTracker::Snapshot, if any.
    std::vector<ActivityUserData::Snapshot> user_data_stack;
  };

  // This class provides keys that uniquely identify a thread, even across
  // multiple processes.
  class ThreadKey {
   public:
    ThreadKey(int64_t pid, int64_t tid) : pid_(pid), tid_(tid) {}

    bool operator<(const ThreadKey& rhs) const {
      if (pid_ != rhs.pid_)
        return pid_ < rhs.pid_;
      return tid_ < rhs.tid_;
    }

    bool operator==(const ThreadKey& rhs) const {
      return (pid_ == rhs.pid_ && tid_ == rhs.tid_);
    }

   private:
    int64_t pid_;
    int64_t tid_;
  };

  // Creates an analyzer for an existing activity |tracker|. A snapshot is taken
  // immediately and the tracker is not referenced again.
  explicit ThreadActivityAnalyzer(const ThreadActivityTracker& tracker);

  // Creates an analyzer for a block of memory currently or previously in-use
  // by an activity-tracker. A snapshot is taken immediately and the memory
  // is not referenced again.
  ThreadActivityAnalyzer(void* base, size_t size);

  // Creates an analyzer for a block of memory held within a persistent-memory
  // |allocator| at the given |reference|. A snapshot is taken immediately and
  // the memory is not referenced again.
  ThreadActivityAnalyzer(PersistentMemoryAllocator* allocator,
                         PersistentMemoryAllocator::Reference reference);

  ~ThreadActivityAnalyzer();

  // Adds information from the global analyzer.
  void AddGlobalInformation(GlobalActivityAnalyzer* global);

  // Returns true iff the contained data is valid. Results from all other
  // methods are undefined if this returns false.
  bool IsValid() { return activity_snapshot_valid_; }

  // Gets the process id and its creation stamp.
  int64_t GetProcessId(int64_t* out_stamp = nullptr) {
    if (out_stamp)
      *out_stamp = activity_snapshot_.create_stamp;
    return activity_snapshot_.process_id;
  }

  // Gets the name of the thread.
  const std::string& GetThreadName() {
    return activity_snapshot_.thread_name;
  }

  // Gets the TheadKey for this thread.
  ThreadKey GetThreadKey() {
    return ThreadKey(activity_snapshot_.process_id,
                     activity_snapshot_.thread_id);
  }

  const Snapshot& activity_snapshot() { return activity_snapshot_; }

 private:
  friend class GlobalActivityAnalyzer;

  // The snapshot of the activity tracker taken at the moment of construction.
  Snapshot activity_snapshot_;

  // Flag indicating if the snapshot data is valid.
  bool activity_snapshot_valid_;

  // A reference into a persistent memory allocator, used by the global
  // analyzer to know where this tracker came from.
  PersistentMemoryAllocator::Reference allocator_reference_ = 0;

  DISALLOW_COPY_AND_ASSIGN(ThreadActivityAnalyzer);
};


// This class manages analyzers for all known processes and threads as stored
// in a persistent memory allocator. It supports retrieval of them through
// iteration and directly using a ThreadKey, which allows for cross-references
// to be resolved.
// Note that though atomic snapshots are used and everything has its snapshot
// taken at the same time, the multi-snapshot itself is not atomic and thus may
// show small inconsistencies between threads if attempted on a live system.
class BASE_EXPORT GlobalActivityAnalyzer {
 public:
  struct ProgramLocation {
    int module;
    uintptr_t offset;
  };

  using ThreadKey = ThreadActivityAnalyzer::ThreadKey;

  // Creates a global analyzer from a persistent memory allocator.
  explicit GlobalActivityAnalyzer(
      std::unique_ptr<PersistentMemoryAllocator> allocator);

  ~GlobalActivityAnalyzer();

  // Creates a global analyzer using a given persistent-memory |allocator|.
  static std::unique_ptr<GlobalActivityAnalyzer> CreateWithAllocator(
      std::unique_ptr<PersistentMemoryAllocator> allocator);

#if !defined(OS_NACL)
  // Creates a global analyzer using the contents of a file given in
  // |file_path|.
  static std::unique_ptr<GlobalActivityAnalyzer> CreateWithFile(
      const FilePath& file_path);
#endif  // !defined(OS_NACL)

  // Like above but accesses an allocator in a mapped shared-memory segment.
  static std::unique_ptr<GlobalActivityAnalyzer> CreateWithSharedMemory(
      std::unique_ptr<SharedMemory> shm);

  // Like above but takes a handle to an existing shared memory segment and
  // maps it before creating the tracker.
  static std::unique_ptr<GlobalActivityAnalyzer> CreateWithSharedMemoryHandle(
      const SharedMemoryHandle& handle,
      size_t size);

  // Iterates over all known valid processes and returns their PIDs or zero
  // if there are no more. Calls to GetFirstProcess() will perform a global
  // snapshot in order to provide a relatively consistent state across the
  // future calls to GetNextProcess() and GetFirst/NextAnalyzer(). PIDs are
  // returned in the order they're found meaning that a first-launched
  // controlling process will be found first. Note, however, that space
  // freed by an exiting process may be re-used by a later process.
  int64_t GetFirstProcess();
  int64_t GetNextProcess();

  // Iterates over all known valid analyzers for the a given process or returns
  // null if there are no more.
  //
  // GetFirstProcess() must be called first in order to capture a global
  // snapshot! Ownership stays with the global analyzer object and all existing
  // analyzer pointers are invalidated when GetFirstProcess() is called.
  ThreadActivityAnalyzer* GetFirstAnalyzer(int64_t pid);
  ThreadActivityAnalyzer* GetNextAnalyzer();

  // Gets the analyzer for a specific thread or null if there is none.
  // Ownership stays with the global analyzer object.
  ThreadActivityAnalyzer* GetAnalyzerForThread(const ThreadKey& key);

  // Extract user data based on a reference and its identifier.
  ActivityUserData::Snapshot GetUserDataSnapshot(int64_t pid,
                                                 uint32_t ref,
                                                 uint32_t id);

  // Extract the data for a specific process. An empty snapshot will be
  // returned if the process is not known.
  const ActivityUserData::Snapshot& GetProcessDataSnapshot(int64_t pid);

  // Gets all log messages stored within.
  std::vector<std::string> GetLogMessages();

  // Gets modules corresponding to a pid. This pid must come from a call to
  // GetFirst/NextProcess. Only modules that were first registered prior to
  // GetFirstProcess's snapshot are returned.
  std::vector<GlobalActivityTracker::ModuleInfo> GetModules(int64_t pid);

  // Gets the corresponding "program location" for a given "program counter".
  // This will return {0,0} if no mapping could be found.
  ProgramLocation GetProgramLocationFromAddress(uint64_t address);

  // Returns whether the data is complete. Data can be incomplete if the
  // recording size quota is hit.
  bool IsDataComplete() const;

 private:
  using AnalyzerMap =
      std::map<ThreadKey, std::unique_ptr<ThreadActivityAnalyzer>>;

  struct UserDataSnapshot {
    // Complex class needs out-of-line ctor/dtor.
    UserDataSnapshot();
    UserDataSnapshot(const UserDataSnapshot& rhs);
    UserDataSnapshot(UserDataSnapshot&& rhs);
    ~UserDataSnapshot();

    int64_t process_id;
    int64_t create_stamp;
    ActivityUserData::Snapshot data;
  };

  // Finds, creates, and indexes analyzers for all known processes and threads.
  void PrepareAllAnalyzers();

  // The persistent memory allocator holding all tracking data.
  std::unique_ptr<PersistentMemoryAllocator> allocator_;

  // The time stamp when analysis began. This is used to prevent looking into
  // process IDs that get reused when analyzing a live system.
  int64_t analysis_stamp_;

  // The iterator for finding tracking information in the allocator.
  PersistentMemoryAllocator::Iterator allocator_iterator_;

  // A set of all interesting memory references found within the allocator.
  std::set<PersistentMemoryAllocator::Reference> memory_references_;

  // A set of all process-data memory references found within the allocator.
  std::map<int64_t, UserDataSnapshot> process_data_;

  // A set of all process IDs collected during PrepareAllAnalyzers. These are
  // popped and returned one-by-one with calls to GetFirst/NextProcess().
  std::vector<int64_t> process_ids_;

  // A map, keyed by ThreadKey, of all valid activity analyzers.
  AnalyzerMap analyzers_;

  // The iterator within the analyzers_ map for returning analyzers through
  // first/next iteration.
  AnalyzerMap::iterator analyzers_iterator_;
  int64_t analyzers_iterator_pid_;

  DISALLOW_COPY_AND_ASSIGN(GlobalActivityAnalyzer);
};

}  // namespace debug
}  // namespace base

#endif  // BASE_DEBUG_ACTIVITY_ANALYZER_H_
