blob: fdf84494242ccbfb5aa8c531a650f1b4c0e5de7b [file] [log] [blame]
// Copyright 2013 Google Inc. All Rights Reserved.
//
// 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.
//
// Defines SampledModuleCache. This is container for storing profiling
// information for many modules that are being profiled across many processes.
// It is intended to be used by a polling monitor which periodically looks for
// new modules to be profiled, and detects when old modules are no longer
// loaded or when processes have terminated.
//
// Because of the polling nature the cache contains mechanisms for doing mark
// and sweep garbage collection. It is intended to be used as follows:
//
// // All modules will be profiled with the same bucket size.
// SampledModuleCache cache(log2_bucket_size);
//
// // Set up a callback that will be invoked when profiling is done for a
// // module.
// cache.set_dead_module_callback(some_callback);
//
// while (... we wish the profiler to continue running...) {
// // Mark all currently profiling modules as dead.
// cache.MarkAllModulesDead();
//
// for (... each module we want to profile ...) {
// // We have a |handle| to the process to be profiled and the
// // |module_handle| to be processed. The module may already be in the
// // process of being profiled, but this will simply mark it as still
// // being alive and eligible for continued profiling. The |status| of the
// // operation and a pointer to the |module| will be set.
// if (cache.AddModule(handle, module_handle, &status, &module))
// ...
// }
//
// // Clean up any modules that haven't been added (or re-added and marked as
// // alive). This invokes our callback with the gathered profile data.
// cache.RemoveDeadModules();
// }
#ifndef SYZYGY_SAMPLER_SAMPLED_MODULE_CACHE_H_
#define SYZYGY_SAMPLER_SAMPLED_MODULE_CACHE_H_
#include <map>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/win/scoped_handle.h"
#include "syzygy/application/application.h"
#include "syzygy/sampler/sampling_profiler.h"
#include "syzygy/trace/common/clock.h"
#include "syzygy/trace/service/process_info.h"
namespace sampler {
class SampledModuleCache {
public:
// Forward declarations. See below for details.
class Process;
class Module;
enum ProfilingStatus {
kProfilingStarted,
kProfilingContinued,
};
typedef std::map<WORD, Process*> ProcessMap;
// This is the callback that is used to indicate that a module has been
// unloaded and/or we have stopped profiling it (from our point of view, it is
// dead). It is up to the callback to deal with the sample data.
typedef base::Callback<void(const Module* module)> DeadModuleCallback;
// Constructor.
// @param log2_bucket_size The number of bits in the bucket size to be used
// by the sampling profiler. This must be in the range 2-31, for bucket
// sizes of 4 bytes to 2 gigabytes. See sampling_profiler.h for more
// details.
explicit SampledModuleCache(size_t log2_bucket_size);
// Destructor.
~SampledModuleCache();
// Sets the callback that is invoked as 'dead' modules are removed from the
// cache.
// @param callback The callback to be invoked. This may be a default
// constructed (empty) callback to indicate that no callback is to be
// invoked.
void set_dead_module_callback(const DeadModuleCallback& callback) {
dead_module_callback_ = callback;
}
// @name Accessors.
// @{
const ProcessMap& processes() const { return processes_; }
size_t log2_bucket_size() const { return log2_bucket_size_; }
const DeadModuleCallback& dead_module_callback() const {
return dead_module_callback_;
}
// @}
// Starts profiling the given module in the given process. If the process and
// module are already being profiled this simply marks them as still alive.
// @param process A handle to the process. This handle will be duplicated
// and the cache will take responsibility for lifetime management of the
// copy.
// @param module_handle The handle to the module to be profiled.
// @param status The status of the profiled module. This will only be set on
// success.
// @param module A pointer to the added module. This will only be non-NULL on
// success.
// @returns true if the process and module were added and profiling was
// successfully started.
bool AddModule(HANDLE process,
HMODULE module_handle,
ProfilingStatus* status,
const Module** module);
// Marks all processes and modules as dead.
void MarkAllModulesDead();
// Cleans up no longer running modules and processes. Prior to removal of a
// module the dead module callback will be invoked, if set.
void RemoveDeadModules();
// @returns the total number of modules currently being profiled across all
// processes.
size_t module_count() const { return module_count_; }
private:
// The set of all processes, and modules within them, that are currently
// begin profiled.
ProcessMap processes_;
// The bucket size to be used by all profiler instances created by this
// cache.
size_t log2_bucket_size_;
// The callback that is being invoked when dead modules are removed, or when
// the entire cache is being destroyed.
DeadModuleCallback dead_module_callback_;
// The total number of modules being profiled across all processes.
size_t module_count_;
DISALLOW_COPY_AND_ASSIGN(SampledModuleCache);
};
// A Process tracks a process containing one or more modules that are
// currently being profiled. Processes are polled so there is no guarantee
// that a tracked process is still running.
class SampledModuleCache::Process {
public:
typedef std::map<HMODULE, Module*> ModuleMap;
// Constructor. Creates a sampled process for the given process handle.
// @param process The handle to the process to be profiled. Ownership of this
// handle is transferred to this object.
// @param pid The PID of the process.
Process(HANDLE process, DWORD pid);
// Destructor.
~Process();
// Initializes this process object.
// @returns true on success, false otherwise.
bool Init();
// @name Accessors.
// @{
HANDLE process() const { return process_.Get(); }
DWORD pid() const { return pid_; }
ModuleMap& modules() { return modules_; }
const ModuleMap& modules() const { return modules_; }
const trace::service::ProcessInfo& process_info() const {
return process_info_;
}
// @}
// Adds the provided module to the set of modules that are being profiled in
// this process. Only returns true if the module is able to be successfully
// queried and the sampling profiler is started.
// @param module_handle The handle to the module to be added.
// @param log2_bucket_size The number of bits in the bucket size to be used
// by the sampling profiler. This must be in the range 2-31, for bucket
// sizes of 4 bytes to 2 gigabytes. See sampling_profiler.h for more
// details.
// @param status The status of the profiled module. This will only be set on
// success.
// @param module A pointer to the added module. This will only be non-NULL on
// success.
// @returns true on success, false otherwise.
bool AddModule(HMODULE module_handle,
size_t log2_bucket_size,
ProfilingStatus* status,
const Module** module);
protected:
friend class SampledModuleCache;
// @name Mark and sweep accessors. These are used internally by the parent
// SampledModuleCache.
// @{
bool alive() const { return alive_; }
void MarkAlive() { alive_ = true; }
void MarkDead();
// @}
// Cleans up no longer running modules.
// @param callback The callback to be invoked on each module just prior to
// removing it. May be empty.
void RemoveDeadModules(DeadModuleCallback callback);
private:
friend class SampledModuleCacheTest; // Testing seam.
// A scoped handle to the running process.
base::win::ScopedHandle process_;
// The process ID of the process. This is used as the key for a
// Process.
DWORD pid_;
// The set of all modules that are currently being profiled.
ModuleMap modules_;
// Information about this process. This is required for writing trace files.
trace::service::ProcessInfo process_info_;
// This is used for cleaning up no longer running processes using a mark and
// sweep technique. The containing SampledModuleCache enforces the invariant
// that alive_ is false if and only if all of our child modules are dead.
bool alive_;
DISALLOW_COPY_AND_ASSIGN(SampledModuleCache::Process);
};
// A Module tracks a module (belonging to a Process) that is currently
// being profiled by an instance of a SamplingProfiler. Modules are polled so
// there is no guarantee that a tracked module is still loaded, nor if its
// parent process is still running.
class SampledModuleCache::Module {
public:
// Constructor.
// @param process The process to which this module belongs.
// @param module The handle to the module to be profiled.
// @param log2_bucket_size The number of bits in the bucket size to be used
// by the sampling profiler. This must be in the range 2-31, for bucket
// sizes of 4 bytes to 2 gigabytes. See sampling_profiler.h for more
// details.
Module(Process* process, HMODULE module, size_t log2_bucket_size);
// @name Accessors.
// @{
Process* process() { return process_; }
const Process* process() const { return process_; }
HMODULE module() const { return module_; }
const base::FilePath& module_path() const { return module_path_; }
size_t module_size() const { return module_size_; }
uint32_t module_checksum() const { return module_checksum_; }
uint32_t module_time_date_stamp() const { return module_time_date_stamp_; }
const void* buckets_begin() const { return buckets_begin_; }
const void* buckets_end() const { return buckets_end_; }
size_t log2_bucket_size() const { return log2_bucket_size_; }
uint64_t profiling_start_time() const { return profiling_start_time_; }
uint64_t profiling_stop_time() const { return profiling_stop_time_; }
SamplingProfiler& profiler() { return profiler_; }
const SamplingProfiler& profiler() const { return profiler_; }
// @}
protected:
friend class SampledModuleCache;
// @name Mark and sweep accessors. These are used internally by the parent
// SampledModuleCache.
// @{
bool alive() const { return alive_; }
void MarkAlive() { alive_ = true; }
void MarkDead() { alive_ = false; }
// @}
// Initializes this module by reaching into the other process and getting
// information about it.
// @returns true on success, false otherwise.
bool Init();
// Starts the sampling profiler.
// @returns true on success, false otherwise.
bool Start();
// Stops the sampling profiler.
// @returns true on success, false otherwise.
bool Stop();
private:
friend class SampledModuleCacheTest; // Testing seam.
// A pointer to the metadata about the process in which this module is loaded.
Process* process_;
// A handle to the module. This is simply a pointer to the base address of the
// module in the other process' address space. Modules are stored in
// a set in their parent Process, keyed off the module handle.
HMODULE module_;
// The path to the module.
base::FilePath module_path_;
// Information that uniquely identifies the module. This information is needed
// when we output the TraceSampleData record to the trace file.
size_t module_size_;
uint32_t module_checksum_;
uint32_t module_time_date_stamp_;
// Information about the portion of the module being profiled. These are
// addresses in the remove module and minimally highlight the .text section
// of the image.
const void* buckets_begin_;
const void* buckets_end_;
size_t log2_bucket_size_;
// The time when we started and stopped profiling this module, as reported by
// RDTSC.
uint64_t profiling_start_time_;
uint64_t profiling_stop_time_;
// The sampling profiler instance that is profiling this module.
SamplingProfiler profiler_;
// This is used for cleaning up no longer loaded modules using a mark and
// sweep technique.
bool alive_;
DISALLOW_COPY_AND_ASSIGN(SampledModuleCache::Module);
};
} // namespace sampler
#endif // SYZYGY_SAMPLER_SAMPLED_MODULE_CACHE_H_