Remove support for logging into a memory buffer.
The only usage of it was in logging tests, I've switched them for using a file. I've left out support for "--logfile=*" for now, as Chromium uses it. Will be removed after the next V8 roll. R=sgjesse@chromium.org BUG=859 TEST=mjsunit/log-* Review URL: http://codereview.chromium.org/7310025 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8629 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
b2e8d72bf5
commit
5c57d0d643
25
include/v8.h
25
include/v8.h
@ -2983,31 +2983,6 @@ class V8EXPORT V8 {
|
||||
*/
|
||||
static bool IsProfilerPaused();
|
||||
|
||||
/**
|
||||
* If logging is performed into a memory buffer (via --logfile=*), allows to
|
||||
* retrieve previously written messages. This can be used for retrieving
|
||||
* profiler log data in the application. This function is thread-safe.
|
||||
*
|
||||
* Caller provides a destination buffer that must exist during GetLogLines
|
||||
* call. Only whole log lines are copied into the buffer.
|
||||
*
|
||||
* \param from_pos specified a point in a buffer to read from, 0 is the
|
||||
* beginning of a buffer. It is assumed that caller updates its current
|
||||
* position using returned size value from the previous call.
|
||||
* \param dest_buf destination buffer for log data.
|
||||
* \param max_size size of the destination buffer.
|
||||
* \returns actual size of log data copied into buffer.
|
||||
*/
|
||||
static int GetLogLines(int from_pos, char* dest_buf, int max_size);
|
||||
|
||||
/**
|
||||
* The minimum allowed size for a log lines buffer. If the size of
|
||||
* the buffer given will not be enough to hold a line of the maximum
|
||||
* length, an attempt to find a log line end in GetLogLines will
|
||||
* fail, and an empty result will be returned.
|
||||
*/
|
||||
static const int kMinimumSizeForLogLinesBuffer = 2048;
|
||||
|
||||
/**
|
||||
* Retrieve the V8 thread id of the calling thread.
|
||||
*
|
||||
|
@ -4842,12 +4842,6 @@ bool V8::IsProfilerPaused() {
|
||||
}
|
||||
|
||||
|
||||
int V8::GetLogLines(int from_pos, char* dest_buf, int max_size) {
|
||||
ASSERT(max_size >= kMinimumSizeForLogLinesBuffer);
|
||||
return LOGGER->GetLogLines(from_pos, dest_buf, max_size);
|
||||
}
|
||||
|
||||
|
||||
int V8::GetCurrentThreadId() {
|
||||
i::Isolate* isolate = i::Isolate::Current();
|
||||
EnsureInitializedForIsolate(isolate, "V8::GetCurrentThreadId()");
|
||||
|
162
src/log-utils.cc
162
src/log-utils.cc
@ -33,99 +33,14 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
LogDynamicBuffer::LogDynamicBuffer(
|
||||
int block_size, int max_size, const char* seal, int seal_size)
|
||||
: block_size_(block_size),
|
||||
max_size_(max_size - (max_size % block_size_)),
|
||||
seal_(seal),
|
||||
seal_size_(seal_size),
|
||||
blocks_(max_size_ / block_size_ + 1),
|
||||
write_pos_(0), block_index_(0), block_write_pos_(0), is_sealed_(false) {
|
||||
ASSERT(BlocksCount() > 0);
|
||||
AllocateBlock(0);
|
||||
for (int i = 1; i < BlocksCount(); ++i) {
|
||||
blocks_[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char* Log::kLogToTemporaryFile = "&";
|
||||
|
||||
LogDynamicBuffer::~LogDynamicBuffer() {
|
||||
for (int i = 0; i < BlocksCount(); ++i) {
|
||||
DeleteArray(blocks_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int LogDynamicBuffer::Read(int from_pos, char* dest_buf, int buf_size) {
|
||||
if (buf_size == 0) return 0;
|
||||
int read_pos = from_pos;
|
||||
int block_read_index = BlockIndex(from_pos);
|
||||
int block_read_pos = PosInBlock(from_pos);
|
||||
int dest_buf_pos = 0;
|
||||
// Read until dest_buf is filled, or write_pos_ encountered.
|
||||
while (read_pos < write_pos_ && dest_buf_pos < buf_size) {
|
||||
const int read_size = Min(write_pos_ - read_pos,
|
||||
Min(buf_size - dest_buf_pos, block_size_ - block_read_pos));
|
||||
memcpy(dest_buf + dest_buf_pos,
|
||||
blocks_[block_read_index] + block_read_pos, read_size);
|
||||
block_read_pos += read_size;
|
||||
dest_buf_pos += read_size;
|
||||
read_pos += read_size;
|
||||
if (block_read_pos == block_size_) {
|
||||
block_read_pos = 0;
|
||||
++block_read_index;
|
||||
}
|
||||
}
|
||||
return dest_buf_pos;
|
||||
}
|
||||
|
||||
|
||||
int LogDynamicBuffer::Seal() {
|
||||
WriteInternal(seal_, seal_size_);
|
||||
is_sealed_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int LogDynamicBuffer::Write(const char* data, int data_size) {
|
||||
if (is_sealed_) {
|
||||
return 0;
|
||||
}
|
||||
if ((write_pos_ + data_size) <= (max_size_ - seal_size_)) {
|
||||
return WriteInternal(data, data_size);
|
||||
} else {
|
||||
return Seal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int LogDynamicBuffer::WriteInternal(const char* data, int data_size) {
|
||||
int data_pos = 0;
|
||||
while (data_pos < data_size) {
|
||||
const int write_size =
|
||||
Min(data_size - data_pos, block_size_ - block_write_pos_);
|
||||
memcpy(blocks_[block_index_] + block_write_pos_, data + data_pos,
|
||||
write_size);
|
||||
block_write_pos_ += write_size;
|
||||
data_pos += write_size;
|
||||
if (block_write_pos_ == block_size_) {
|
||||
block_write_pos_ = 0;
|
||||
AllocateBlock(++block_index_);
|
||||
}
|
||||
}
|
||||
write_pos_ += data_size;
|
||||
return data_size;
|
||||
}
|
||||
|
||||
// Must be the same message as in Logger::PauseProfiler.
|
||||
const char* const Log::kDynamicBufferSeal = "profiler,\"pause\"\n";
|
||||
|
||||
Log::Log(Logger* logger)
|
||||
: write_to_file_(false),
|
||||
is_stopped_(false),
|
||||
: is_stopped_(false),
|
||||
output_handle_(NULL),
|
||||
ll_output_handle_(NULL),
|
||||
output_buffer_(NULL),
|
||||
mutex_(NULL),
|
||||
message_buffer_(NULL),
|
||||
logger_(logger) {
|
||||
@ -163,19 +78,19 @@ void Log::Initialize() {
|
||||
FLAG_prof_auto = false;
|
||||
}
|
||||
|
||||
bool start_logging = FLAG_log || FLAG_log_runtime || FLAG_log_api
|
||||
bool open_log_file = FLAG_log || FLAG_log_runtime || FLAG_log_api
|
||||
|| FLAG_log_code || FLAG_log_gc || FLAG_log_handles || FLAG_log_suspect
|
||||
|| FLAG_log_regexp || FLAG_log_state_changes || FLAG_ll_prof;
|
||||
|
||||
bool open_log_file = start_logging || FLAG_prof_lazy;
|
||||
|
||||
// If we're logging anything, we need to open the log file.
|
||||
if (open_log_file) {
|
||||
if (strcmp(FLAG_logfile, "-") == 0) {
|
||||
OpenStdout();
|
||||
} else if (strcmp(FLAG_logfile, "*") == 0) {
|
||||
OpenMemoryBuffer();
|
||||
} else {
|
||||
// Does nothing for now. Will be removed.
|
||||
} else if (strcmp(FLAG_logfile, kLogToTemporaryFile) == 0) {
|
||||
OpenTemporaryFile();
|
||||
} else {
|
||||
if (strchr(FLAG_logfile, '%') != NULL ||
|
||||
!Isolate::Current()->IsDefaultIsolate()) {
|
||||
// If there's a '%' in the log file name we have to expand
|
||||
@ -225,7 +140,12 @@ void Log::Initialize() {
|
||||
void Log::OpenStdout() {
|
||||
ASSERT(!IsEnabled());
|
||||
output_handle_ = stdout;
|
||||
write_to_file_ = true;
|
||||
}
|
||||
|
||||
|
||||
void Log::OpenTemporaryFile() {
|
||||
ASSERT(!IsEnabled());
|
||||
output_handle_ = i::OS::OpenTemporaryFile();
|
||||
}
|
||||
|
||||
|
||||
@ -240,7 +160,6 @@ static const int kLowLevelLogBufferSize = 2 * MB;
|
||||
void Log::OpenFile(const char* name) {
|
||||
ASSERT(!IsEnabled());
|
||||
output_handle_ = OS::FOpen(name, OS::LogFileOpenMode);
|
||||
write_to_file_ = true;
|
||||
if (FLAG_ll_prof) {
|
||||
// Open the low-level log file.
|
||||
size_t len = strlen(name);
|
||||
@ -253,25 +172,18 @@ void Log::OpenFile(const char* name) {
|
||||
}
|
||||
|
||||
|
||||
void Log::OpenMemoryBuffer() {
|
||||
ASSERT(!IsEnabled());
|
||||
output_buffer_ = new LogDynamicBuffer(
|
||||
kDynamicBufferBlockSize, kMaxDynamicBufferSize,
|
||||
kDynamicBufferSeal, StrLength(kDynamicBufferSeal));
|
||||
write_to_file_ = false;
|
||||
}
|
||||
|
||||
|
||||
void Log::Close() {
|
||||
if (write_to_file_) {
|
||||
if (output_handle_ != NULL) fclose(output_handle_);
|
||||
output_handle_ = NULL;
|
||||
if (ll_output_handle_ != NULL) fclose(ll_output_handle_);
|
||||
ll_output_handle_ = NULL;
|
||||
} else {
|
||||
delete output_buffer_;
|
||||
output_buffer_ = NULL;
|
||||
FILE* Log::Close() {
|
||||
FILE* result = NULL;
|
||||
if (output_handle_ != NULL) {
|
||||
if (strcmp(FLAG_logfile, kLogToTemporaryFile) != 0) {
|
||||
fclose(output_handle_);
|
||||
} else {
|
||||
result = output_handle_;
|
||||
}
|
||||
}
|
||||
output_handle_ = NULL;
|
||||
if (ll_output_handle_ != NULL) fclose(ll_output_handle_);
|
||||
ll_output_handle_ = NULL;
|
||||
|
||||
DeleteArray(message_buffer_);
|
||||
message_buffer_ = NULL;
|
||||
@ -280,27 +192,7 @@ void Log::Close() {
|
||||
mutex_ = NULL;
|
||||
|
||||
is_stopped_ = false;
|
||||
}
|
||||
|
||||
|
||||
int Log::GetLogLines(int from_pos, char* dest_buf, int max_size) {
|
||||
if (write_to_file_) return 0;
|
||||
ASSERT(output_buffer_ != NULL);
|
||||
ASSERT(from_pos >= 0);
|
||||
ASSERT(max_size >= 0);
|
||||
int actual_size = output_buffer_->Read(from_pos, dest_buf, max_size);
|
||||
ASSERT(actual_size <= max_size);
|
||||
if (actual_size == 0) return 0;
|
||||
|
||||
// Find previous log line boundary.
|
||||
char* end_pos = dest_buf + actual_size - 1;
|
||||
while (end_pos >= dest_buf && *end_pos != '\n') --end_pos;
|
||||
actual_size = static_cast<int>(end_pos - dest_buf + 1);
|
||||
// If the assertion below is hit, it means that there was no line end
|
||||
// found --- something wrong has happened.
|
||||
ASSERT(actual_size > 0);
|
||||
ASSERT(actual_size <= max_size);
|
||||
return actual_size;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -409,9 +301,7 @@ void LogMessageBuilder::AppendStringPart(const char* str, int len) {
|
||||
|
||||
void LogMessageBuilder::WriteToLogFile() {
|
||||
ASSERT(pos_ <= Log::kMessageBufferSize);
|
||||
const int written = log_->write_to_file_ ?
|
||||
log_->WriteToFile(log_->message_buffer_, pos_) :
|
||||
log_->WriteToMemory(log_->message_buffer_, pos_);
|
||||
const int written = log_->WriteToFile(log_->message_buffer_, pos_);
|
||||
if (written != pos_) {
|
||||
log_->stop();
|
||||
log_->logger_->LogFailure();
|
||||
|
101
src/log-utils.h
101
src/log-utils.h
@ -35,65 +35,9 @@ namespace internal {
|
||||
|
||||
class Logger;
|
||||
|
||||
// A memory buffer that increments its size as you write in it. Size
|
||||
// is incremented with 'block_size' steps, never exceeding 'max_size'.
|
||||
// During growth, memory contents are never copied. At the end of the
|
||||
// buffer an amount of memory specified in 'seal_size' is reserved.
|
||||
// When writing position reaches max_size - seal_size, buffer auto-seals
|
||||
// itself with 'seal' and allows no further writes. Data pointed by
|
||||
// 'seal' must be available during entire LogDynamicBuffer lifetime.
|
||||
//
|
||||
// An instance of this class is created dynamically by Log.
|
||||
class LogDynamicBuffer {
|
||||
public:
|
||||
LogDynamicBuffer(
|
||||
int block_size, int max_size, const char* seal, int seal_size);
|
||||
|
||||
~LogDynamicBuffer();
|
||||
|
||||
// Reads contents of the buffer starting from 'from_pos'. Upon
|
||||
// return, 'dest_buf' is filled with the data. Actual amount of data
|
||||
// filled is returned, it is <= 'buf_size'.
|
||||
int Read(int from_pos, char* dest_buf, int buf_size);
|
||||
|
||||
// Writes 'data' to the buffer, making it larger if necessary. If
|
||||
// data is too big to fit in the buffer, it doesn't get written at
|
||||
// all. In that case, buffer auto-seals itself and stops to accept
|
||||
// any incoming writes. Returns amount of data written (it is either
|
||||
// 'data_size', or 0, if 'data' is too big).
|
||||
int Write(const char* data, int data_size);
|
||||
|
||||
private:
|
||||
void AllocateBlock(int index) {
|
||||
blocks_[index] = NewArray<char>(block_size_);
|
||||
}
|
||||
|
||||
int BlockIndex(int pos) const { return pos / block_size_; }
|
||||
|
||||
int BlocksCount() const { return BlockIndex(max_size_) + 1; }
|
||||
|
||||
int PosInBlock(int pos) const { return pos % block_size_; }
|
||||
|
||||
int Seal();
|
||||
|
||||
int WriteInternal(const char* data, int data_size);
|
||||
|
||||
const int block_size_;
|
||||
const int max_size_;
|
||||
const char* seal_;
|
||||
const int seal_size_;
|
||||
ScopedVector<char*> blocks_;
|
||||
int write_pos_;
|
||||
int block_index_;
|
||||
int block_write_pos_;
|
||||
bool is_sealed_;
|
||||
};
|
||||
|
||||
|
||||
// Functions and data for performing output of log messages.
|
||||
class Log {
|
||||
public:
|
||||
|
||||
// Performs process-wide initialization.
|
||||
void Initialize();
|
||||
|
||||
@ -101,18 +45,21 @@ class Log {
|
||||
void stop() { is_stopped_ = true; }
|
||||
|
||||
// Frees all resources acquired in Initialize and Open... functions.
|
||||
void Close();
|
||||
|
||||
// See description in include/v8.h.
|
||||
int GetLogLines(int from_pos, char* dest_buf, int max_size);
|
||||
// When a temporary file is used for the log, returns its stream descriptor,
|
||||
// leaving the file open.
|
||||
FILE* Close();
|
||||
|
||||
// Returns whether logging is enabled.
|
||||
bool IsEnabled() {
|
||||
return !is_stopped_ && (output_handle_ != NULL || output_buffer_ != NULL);
|
||||
return !is_stopped_ && output_handle_ != NULL;
|
||||
}
|
||||
|
||||
// Size of buffer used for formatting log messages.
|
||||
static const int kMessageBufferSize = v8::V8::kMinimumSizeForLogLinesBuffer;
|
||||
static const int kMessageBufferSize = 2048;
|
||||
|
||||
// This mode is only used in tests, as temporary files are automatically
|
||||
// deleted on close and thus can't be accessed afterwards.
|
||||
static const char* kLogToTemporaryFile;
|
||||
|
||||
private:
|
||||
explicit Log(Logger* logger);
|
||||
@ -123,8 +70,8 @@ class Log {
|
||||
// Opens file for logging.
|
||||
void OpenFile(const char* name);
|
||||
|
||||
// Opens memory buffer for logging.
|
||||
void OpenMemoryBuffer();
|
||||
// Opens a temporary file for logging.
|
||||
void OpenTemporaryFile();
|
||||
|
||||
// Implementation of writing to a log file.
|
||||
int WriteToFile(const char* msg, int length) {
|
||||
@ -136,38 +83,16 @@ class Log {
|
||||
return length;
|
||||
}
|
||||
|
||||
// Implementation of writing to a memory buffer.
|
||||
int WriteToMemory(const char* msg, int length) {
|
||||
ASSERT(output_buffer_ != NULL);
|
||||
return output_buffer_->Write(msg, length);
|
||||
}
|
||||
|
||||
bool write_to_file_;
|
||||
|
||||
// Whether logging is stopped (e.g. due to insufficient resources).
|
||||
bool is_stopped_;
|
||||
|
||||
// When logging is active, either output_handle_ or output_buffer_ is used
|
||||
// to store a pointer to log destination. If logging was opened via OpenStdout
|
||||
// or OpenFile, then output_handle_ is used. If logging was opened
|
||||
// via OpenMemoryBuffer, then output_buffer_ is used.
|
||||
// mutex_ should be acquired before using output_handle_ or output_buffer_.
|
||||
// When logging is active output_handle_ is used to store a pointer to log
|
||||
// destination. mutex_ should be acquired before using output_handle_.
|
||||
FILE* output_handle_;
|
||||
|
||||
// Used when low-level profiling is active.
|
||||
FILE* ll_output_handle_;
|
||||
|
||||
LogDynamicBuffer* output_buffer_;
|
||||
|
||||
// Size of dynamic buffer block (and dynamic buffer initial size).
|
||||
static const int kDynamicBufferBlockSize = 65536;
|
||||
|
||||
// Maximum size of dynamic buffer.
|
||||
static const int kMaxDynamicBufferSize = 50 * 1024 * 1024;
|
||||
|
||||
// Message to "seal" dynamic buffer with.
|
||||
static const char* const kDynamicBufferSeal;
|
||||
|
||||
// mutex_ is a Mutex used for enforcing exclusive
|
||||
// access to the formatting buffer and the log file or log memory buffer.
|
||||
Mutex* mutex_;
|
||||
|
12
src/log.cc
12
src/log.cc
@ -1281,7 +1281,6 @@ void Logger::PauseProfiler() {
|
||||
ticker_->Stop();
|
||||
}
|
||||
FLAG_log_code = false;
|
||||
// Must be the same message as Log::kDynamicBufferSeal.
|
||||
LOG(ISOLATE, UncheckedStringEvent("profiler", "pause"));
|
||||
}
|
||||
--logging_nesting_;
|
||||
@ -1323,11 +1322,6 @@ bool Logger::IsProfilerSamplerActive() {
|
||||
}
|
||||
|
||||
|
||||
int Logger::GetLogLines(int from_pos, char* dest_buf, int max_size) {
|
||||
return log_->GetLogLines(from_pos, dest_buf, max_size);
|
||||
}
|
||||
|
||||
|
||||
class EnumerateOptimizedFunctionsVisitor: public OptimizedFunctionVisitor {
|
||||
public:
|
||||
EnumerateOptimizedFunctionsVisitor(Handle<SharedFunctionInfo>* sfis,
|
||||
@ -1683,8 +1677,8 @@ void Logger::EnsureTickerStopped() {
|
||||
}
|
||||
|
||||
|
||||
void Logger::TearDown() {
|
||||
if (!is_initialized_) return;
|
||||
FILE* Logger::TearDown() {
|
||||
if (!is_initialized_) return NULL;
|
||||
is_initialized_ = false;
|
||||
|
||||
// Stop the profiler before closing the file.
|
||||
@ -1700,7 +1694,7 @@ void Logger::TearDown() {
|
||||
delete ticker_;
|
||||
ticker_ = NULL;
|
||||
|
||||
log_->Close();
|
||||
return log_->Close();
|
||||
}
|
||||
|
||||
|
||||
|
@ -157,7 +157,9 @@ class Logger {
|
||||
Sampler* sampler();
|
||||
|
||||
// Frees resources acquired in Setup.
|
||||
void TearDown();
|
||||
// When a temporary file is used for the log, returns its stream descriptor,
|
||||
// leaving the file open.
|
||||
FILE* TearDown();
|
||||
|
||||
// Enable the computation of a sliding window of states.
|
||||
void EnableSlidingStateWindow();
|
||||
@ -279,10 +281,6 @@ class Logger {
|
||||
void ResumeProfiler();
|
||||
bool IsProfilerPaused();
|
||||
|
||||
// If logging is performed into a memory buffer, allows to
|
||||
// retrieve previously written messages. See v8.h.
|
||||
int GetLogLines(int from_pos, char* dest_buf, int max_size);
|
||||
|
||||
// Logs all compiled functions found in the heap.
|
||||
void LogCompiledFunctions();
|
||||
// Logs all accessor callbacks found in the heap.
|
||||
|
@ -147,6 +147,11 @@ bool OS::Remove(const char* path) {
|
||||
}
|
||||
|
||||
|
||||
FILE* OS::OpenTemporaryFile() {
|
||||
return tmpfile();
|
||||
}
|
||||
|
||||
|
||||
const char* const OS::LogFileOpenMode = "w";
|
||||
|
||||
|
||||
|
@ -740,6 +740,24 @@ bool OS::Remove(const char* path) {
|
||||
}
|
||||
|
||||
|
||||
FILE* OS::OpenTemporaryFile() {
|
||||
// tmpfile_s tries to use the root dir, don't use it.
|
||||
char tempPathBuffer[MAX_PATH];
|
||||
DWORD path_result = 0;
|
||||
path_result = GetTempPath(MAX_PATH, tempPathBuffer);
|
||||
if (path_result > MAX_PATH || path_result == 0) return NULL;
|
||||
UINT name_result = 0;
|
||||
char tempNameBuffer[MAX_PATH];
|
||||
name_result = GetTempFileName(tempPathBuffer, "", 0, tempNameBuffer);
|
||||
if (name_result == 0) return NULL;
|
||||
FILE* result = FOpen(tempNameBuffer, "w+"); // Same mode as tmpfile uses.
|
||||
if (result != NULL) {
|
||||
Remove(tempNameBuffer); // Delete on close.
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Open log file in binary mode to avoid /n -> /r/n conversion.
|
||||
const char* const OS::LogFileOpenMode = "wb";
|
||||
|
||||
|
@ -177,6 +177,9 @@ class OS {
|
||||
static FILE* FOpen(const char* path, const char* mode);
|
||||
static bool Remove(const char* path);
|
||||
|
||||
// Opens a temporary file, the file is auto removed on close.
|
||||
static FILE* OpenTemporaryFile();
|
||||
|
||||
// Log file open mode is platform-dependent due to line ends issues.
|
||||
static const char* const LogFileOpenMode;
|
||||
|
||||
|
@ -110,11 +110,11 @@ char* ReadLine(const char* prompt) {
|
||||
}
|
||||
|
||||
|
||||
char* ReadCharsFromFile(const char* filename,
|
||||
char* ReadCharsFromFile(FILE* file,
|
||||
int* size,
|
||||
int extra_space,
|
||||
bool verbose) {
|
||||
FILE* file = OS::FOpen(filename, "rb");
|
||||
bool verbose,
|
||||
const char* filename) {
|
||||
if (file == NULL || fseek(file, 0, SEEK_END) != 0) {
|
||||
if (verbose) {
|
||||
OS::PrintError("Cannot read from file %s.\n", filename);
|
||||
@ -127,16 +127,26 @@ char* ReadCharsFromFile(const char* filename,
|
||||
rewind(file);
|
||||
|
||||
char* result = NewArray<char>(*size + extra_space);
|
||||
for (int i = 0; i < *size;) {
|
||||
for (int i = 0; i < *size && feof(file) == 0;) {
|
||||
int read = static_cast<int>(fread(&result[i], 1, *size - i, file));
|
||||
if (read <= 0) {
|
||||
if (read != (*size - i) && ferror(file) != 0) {
|
||||
fclose(file);
|
||||
DeleteArray(result);
|
||||
return NULL;
|
||||
}
|
||||
i += read;
|
||||
}
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
char* ReadCharsFromFile(const char* filename,
|
||||
int* size,
|
||||
int extra_space,
|
||||
bool verbose) {
|
||||
FILE* file = OS::FOpen(filename, "rb");
|
||||
char* result = ReadCharsFromFile(file, size, extra_space, verbose, filename);
|
||||
if (file != NULL) fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -147,18 +157,34 @@ byte* ReadBytes(const char* filename, int* size, bool verbose) {
|
||||
}
|
||||
|
||||
|
||||
static Vector<const char> SetVectorContents(char* chars,
|
||||
int size,
|
||||
bool* exists) {
|
||||
if (!chars) {
|
||||
*exists = false;
|
||||
return Vector<const char>::empty();
|
||||
}
|
||||
chars[size] = '\0';
|
||||
*exists = true;
|
||||
return Vector<const char>(chars, size);
|
||||
}
|
||||
|
||||
|
||||
Vector<const char> ReadFile(const char* filename,
|
||||
bool* exists,
|
||||
bool verbose) {
|
||||
int size;
|
||||
char* result = ReadCharsFromFile(filename, &size, 1, verbose);
|
||||
if (!result) {
|
||||
*exists = false;
|
||||
return Vector<const char>::empty();
|
||||
}
|
||||
result[size] = '\0';
|
||||
*exists = true;
|
||||
return Vector<const char>(result, size);
|
||||
return SetVectorContents(result, size, exists);
|
||||
}
|
||||
|
||||
|
||||
Vector<const char> ReadFile(FILE* file,
|
||||
bool* exists,
|
||||
bool verbose) {
|
||||
int size;
|
||||
char* result = ReadCharsFromFile(file, &size, 1, verbose, "");
|
||||
return SetVectorContents(result, size, exists);
|
||||
}
|
||||
|
||||
|
||||
|
@ -188,6 +188,9 @@ class AsciiStringAdapter: public v8::String::ExternalAsciiStringResource {
|
||||
Vector<const char> ReadFile(const char* filename,
|
||||
bool* exists,
|
||||
bool verbose = true);
|
||||
Vector<const char> ReadFile(FILE* file,
|
||||
bool* exists,
|
||||
bool verbose = true);
|
||||
|
||||
|
||||
|
||||
|
@ -65,7 +65,6 @@ SOURCES = {
|
||||
'test-liveedit.cc',
|
||||
'test-lock.cc',
|
||||
'test-lockers.cc',
|
||||
'test-log-utils.cc',
|
||||
'test-log.cc',
|
||||
'test-mark-compact.cc',
|
||||
'test-parsing.cc',
|
||||
|
@ -71,7 +71,6 @@
|
||||
'test-lock.cc',
|
||||
'test-lockers.cc',
|
||||
'test-log.cc',
|
||||
'test-log-utils.cc',
|
||||
'test-mark-compact.cc',
|
||||
'test-parsing.cc',
|
||||
'test-profile-generator.cc',
|
||||
|
191
test/cctest/log-eq-of-logging-and-traversal.js
Normal file
191
test/cctest/log-eq-of-logging-and-traversal.js
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright 2011 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// This is a supplementary file for test-log/EquivalenceOfLoggingAndTraversal.
|
||||
|
||||
function parseState(s) {
|
||||
switch (s) {
|
||||
case "": return Profile.CodeState.COMPILED;
|
||||
case "~": return Profile.CodeState.OPTIMIZABLE;
|
||||
case "*": return Profile.CodeState.OPTIMIZED;
|
||||
}
|
||||
throw new Error("unknown code state: " + s);
|
||||
}
|
||||
|
||||
function LogProcessor() {
|
||||
LogReader.call(this, {
|
||||
'code-creation': {
|
||||
parsers: [null, parseInt, parseInt, null, 'var-args'],
|
||||
processor: this.processCodeCreation },
|
||||
'code-move': { parsers: [parseInt, parseInt],
|
||||
processor: this.processCodeMove },
|
||||
'code-delete': { parsers: [parseInt],
|
||||
processor: this.processCodeDelete },
|
||||
'sfi-move': { parsers: [parseInt, parseInt],
|
||||
processor: this.processFunctionMove },
|
||||
'shared-library': null,
|
||||
'profiler': null,
|
||||
'tick': null });
|
||||
this.profile = new Profile();
|
||||
|
||||
}
|
||||
LogProcessor.prototype.__proto__ = LogReader.prototype;
|
||||
|
||||
LogProcessor.prototype.processCodeCreation = function(
|
||||
type, start, size, name, maybe_func) {
|
||||
if (type != "LazyCompile" && type != "Script" && type != "Function") return;
|
||||
// Discard types to avoid discrepancies in "LazyCompile" vs. "Function".
|
||||
type = "";
|
||||
if (maybe_func.length) {
|
||||
var funcAddr = parseInt(maybe_func[0]);
|
||||
var state = parseState(maybe_func[1]);
|
||||
this.profile.addFuncCode(type, name, start, size, funcAddr, state);
|
||||
} else {
|
||||
this.profile.addCode(type, name, start, size);
|
||||
}
|
||||
};
|
||||
|
||||
LogProcessor.prototype.processCodeMove = function(from, to) {
|
||||
this.profile.moveCode(from, to);
|
||||
};
|
||||
|
||||
LogProcessor.prototype.processCodeDelete = function(start) {
|
||||
this.profile.deleteCode(start);
|
||||
};
|
||||
|
||||
LogProcessor.prototype.processFunctionMove = function(from, to) {
|
||||
this.profile.moveFunc(from, to);
|
||||
};
|
||||
|
||||
function RunTest() {
|
||||
// _log must be provided externally.
|
||||
var log_lines = _log.split("\n");
|
||||
var line, pos = 0, log_lines_length = log_lines.length;
|
||||
if (log_lines_length < 2)
|
||||
return "log_lines_length < 2";
|
||||
var logging_processor = new LogProcessor();
|
||||
for ( ; pos < log_lines_length; ++pos) {
|
||||
line = log_lines[pos];
|
||||
if (line === "test-logging-done,\"\"") {
|
||||
++pos;
|
||||
break;
|
||||
}
|
||||
logging_processor.processLogLine(line);
|
||||
}
|
||||
logging_processor.profile.cleanUpFuncEntries();
|
||||
var logging_entries =
|
||||
logging_processor.profile.codeMap_.getAllDynamicEntriesWithAddresses();
|
||||
if (logging_entries.length === 0)
|
||||
return "logging_entries.length === 0";
|
||||
var traversal_processor = new LogProcessor();
|
||||
for ( ; pos < log_lines_length; ++pos) {
|
||||
line = log_lines[pos];
|
||||
if (line === "test-traversal-done,\"\"") break;
|
||||
traversal_processor.processLogLine(line);
|
||||
}
|
||||
var traversal_entries =
|
||||
traversal_processor.profile.codeMap_.getAllDynamicEntriesWithAddresses();
|
||||
if (traversal_entries.length === 0)
|
||||
return "traversal_entries.length === 0";
|
||||
|
||||
function addressComparator(entryA, entryB) {
|
||||
return entryA[0] < entryB[0] ? -1 : (entryA[0] > entryB[0] ? 1 : 0);
|
||||
}
|
||||
|
||||
logging_entries.sort(addressComparator);
|
||||
traversal_entries.sort(addressComparator);
|
||||
|
||||
function entityNamesEqual(entityA, entityB) {
|
||||
if ("getRawName" in entityB &&
|
||||
entityNamesEqual.builtins.indexOf(entityB.getRawName()) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (entityNamesEqual.builtins.indexOf(entityB.getName()) !== -1) return true;
|
||||
return entityA.getName() === entityB.getName();
|
||||
}
|
||||
entityNamesEqual.builtins =
|
||||
["Boolean", "Function", "Number", "Object",
|
||||
"Script", "String", "RegExp", "Date", "Error"];
|
||||
|
||||
function entitiesEqual(entityA, entityB) {
|
||||
if (entityA === null && entityB !== null) return true;
|
||||
if (entityA !== null && entityB === null) return false;
|
||||
return entityA.size === entityB.size && entityNamesEqual(entityA, entityB);
|
||||
}
|
||||
|
||||
var i = 0, j = 0, k = logging_entries.length, l = traversal_entries.length;
|
||||
var comparison = [];
|
||||
var equal = true;
|
||||
// Do a merge-like comparison of entries. At the same address we expect to
|
||||
// find the same entries. We skip builtins during log parsing, but compiled
|
||||
// functions traversal may erroneously recognize them as functions, so we are
|
||||
// expecting more functions in traversal vs. logging.
|
||||
while (i < k && j < l) {
|
||||
var entryA = logging_entries[i], entryB = traversal_entries[j];
|
||||
var cmp = addressComparator(entryA, entryB);
|
||||
var entityA = entryA[1], entityB = entryB[1];
|
||||
var address = entryA[0];
|
||||
if (cmp < 0) {
|
||||
++i;
|
||||
entityB = null;
|
||||
} else if (cmp > 0) {
|
||||
++j;
|
||||
entityA = null;
|
||||
address = entryB[0];
|
||||
} else {
|
||||
++i;
|
||||
++j;
|
||||
}
|
||||
var entities_equal = entitiesEqual(entityA, entityB);
|
||||
if (!entities_equal) equal = false;
|
||||
comparison.push([entities_equal, address, entityA, entityB]);
|
||||
}
|
||||
if (i < k) equal = false;
|
||||
while (i < k) {
|
||||
var entryA = logging_entries[i++];
|
||||
comparison.push([false, entryA[0], entryA[1], null]);
|
||||
}
|
||||
return [equal, comparison];
|
||||
}
|
||||
|
||||
var result = RunTest();
|
||||
if (typeof result !== "string") {
|
||||
var out = [];
|
||||
if (!result[0]) {
|
||||
var comparison = result[1];
|
||||
for (var i = 0, l = comparison.length; i < l; ++i) {
|
||||
var c = comparison[i];
|
||||
out.push((c[0] ? " " : "* ") +
|
||||
c[1].toString(16) + " " +
|
||||
(c[2] ? c[2] : "---") + " " +
|
||||
(c[3] ? c[3] : "---"));
|
||||
}
|
||||
}
|
||||
result[0] ? true : out.join("\n");
|
||||
} else {
|
||||
result;
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
// Copyright 2006-2009 the V8 project authors. All rights reserved.
|
||||
//
|
||||
// Tests of logging utilities from log-utils.h
|
||||
|
||||
#include "v8.h"
|
||||
|
||||
#include "log-utils.h"
|
||||
#include "cctest.h"
|
||||
|
||||
using v8::internal::CStrVector;
|
||||
using v8::internal::EmbeddedVector;
|
||||
using v8::internal::LogDynamicBuffer;
|
||||
using v8::internal::MutableCStrVector;
|
||||
using v8::internal::ScopedVector;
|
||||
using v8::internal::Vector;
|
||||
using v8::internal::StrLength;
|
||||
|
||||
// Fills 'ref_buffer' with test data: a sequence of two-digit
|
||||
// hex numbers: '0001020304...'. Then writes 'ref_buffer' contents to 'dynabuf'.
|
||||
static void WriteData(LogDynamicBuffer* dynabuf, Vector<char>* ref_buffer) {
|
||||
static const char kHex[] = "0123456789ABCDEF";
|
||||
CHECK_GT(ref_buffer->length(), 0);
|
||||
CHECK_GT(513, ref_buffer->length());
|
||||
for (int i = 0, half_len = ref_buffer->length() >> 1; i < half_len; ++i) {
|
||||
(*ref_buffer)[i << 1] = kHex[i >> 4];
|
||||
(*ref_buffer)[(i << 1) + 1] = kHex[i & 15];
|
||||
}
|
||||
if (ref_buffer->length() & 1) {
|
||||
ref_buffer->last() = kHex[ref_buffer->length() >> 5];
|
||||
}
|
||||
CHECK_EQ(ref_buffer->length(),
|
||||
dynabuf->Write(ref_buffer->start(), ref_buffer->length()));
|
||||
}
|
||||
|
||||
|
||||
static int ReadData(
|
||||
LogDynamicBuffer* dynabuf, int start_pos, i::Vector<char>* buffer) {
|
||||
return dynabuf->Read(start_pos, buffer->start(), buffer->length());
|
||||
}
|
||||
|
||||
|
||||
// Helper function used by CHECK_EQ to compare Vectors. Templatized to
|
||||
// accept both "char" and "const char" vector contents.
|
||||
template <typename E, typename V>
|
||||
static inline void CheckEqualsHelper(const char* file, int line,
|
||||
const char* expected_source,
|
||||
const Vector<E>& expected,
|
||||
const char* value_source,
|
||||
const Vector<V>& value) {
|
||||
if (expected.length() != value.length()) {
|
||||
V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n"
|
||||
"# Vectors lengths differ: %d expected, %d found\n"
|
||||
"# Expected: %.*s\n"
|
||||
"# Found: %.*s",
|
||||
expected_source, value_source,
|
||||
expected.length(), value.length(),
|
||||
expected.length(), expected.start(),
|
||||
value.length(), value.start());
|
||||
}
|
||||
if (strncmp(expected.start(), value.start(), expected.length()) != 0) {
|
||||
V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n"
|
||||
"# Vectors contents differ:\n"
|
||||
"# Expected: %.*s\n"
|
||||
"# Found: %.*s",
|
||||
expected_source, value_source,
|
||||
expected.length(), expected.start(),
|
||||
value.length(), value.start());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(DynaBufSingleBlock) {
|
||||
LogDynamicBuffer dynabuf(32, 32, "", 0);
|
||||
EmbeddedVector<char, 32> ref_buf;
|
||||
WriteData(&dynabuf, &ref_buf);
|
||||
EmbeddedVector<char, 32> buf;
|
||||
CHECK_EQ(32, dynabuf.Read(0, buf.start(), buf.length()));
|
||||
CHECK_EQ(32, ReadData(&dynabuf, 0, &buf));
|
||||
CHECK_EQ(ref_buf, buf);
|
||||
|
||||
// Verify that we can't read and write past the end.
|
||||
CHECK_EQ(0, dynabuf.Read(32, buf.start(), buf.length()));
|
||||
CHECK_EQ(0, dynabuf.Write(buf.start(), buf.length()));
|
||||
}
|
||||
|
||||
|
||||
TEST(DynaBufCrossBlocks) {
|
||||
LogDynamicBuffer dynabuf(32, 128, "", 0);
|
||||
EmbeddedVector<char, 48> ref_buf;
|
||||
WriteData(&dynabuf, &ref_buf);
|
||||
CHECK_EQ(48, dynabuf.Write(ref_buf.start(), ref_buf.length()));
|
||||
// Verify that we can't write data when remaining buffer space isn't enough.
|
||||
CHECK_EQ(0, dynabuf.Write(ref_buf.start(), ref_buf.length()));
|
||||
EmbeddedVector<char, 48> buf;
|
||||
CHECK_EQ(48, ReadData(&dynabuf, 0, &buf));
|
||||
CHECK_EQ(ref_buf, buf);
|
||||
CHECK_EQ(48, ReadData(&dynabuf, 48, &buf));
|
||||
CHECK_EQ(ref_buf, buf);
|
||||
CHECK_EQ(0, ReadData(&dynabuf, 48 * 2, &buf));
|
||||
}
|
||||
|
||||
|
||||
TEST(DynaBufReadTruncation) {
|
||||
LogDynamicBuffer dynabuf(32, 128, "", 0);
|
||||
EmbeddedVector<char, 128> ref_buf;
|
||||
WriteData(&dynabuf, &ref_buf);
|
||||
EmbeddedVector<char, 128> buf;
|
||||
CHECK_EQ(128, ReadData(&dynabuf, 0, &buf));
|
||||
CHECK_EQ(ref_buf, buf);
|
||||
// Try to read near the end with a buffer larger than remaining data size.
|
||||
EmbeddedVector<char, 48> tail_buf;
|
||||
CHECK_EQ(32, ReadData(&dynabuf, 128 - 32, &tail_buf));
|
||||
CHECK_EQ(ref_buf.SubVector(128 - 32, 128), tail_buf.SubVector(0, 32));
|
||||
}
|
||||
|
||||
|
||||
TEST(DynaBufSealing) {
|
||||
const char* seal = "Sealed";
|
||||
const int seal_size = StrLength(seal);
|
||||
LogDynamicBuffer dynabuf(32, 128, seal, seal_size);
|
||||
EmbeddedVector<char, 100> ref_buf;
|
||||
WriteData(&dynabuf, &ref_buf);
|
||||
// Try to write data that will not fit in the buffer.
|
||||
CHECK_EQ(0, dynabuf.Write(ref_buf.start(), 128 - 100 - seal_size + 1));
|
||||
// Now the buffer is sealed, writing of any amount of data is forbidden.
|
||||
CHECK_EQ(0, dynabuf.Write(ref_buf.start(), 1));
|
||||
EmbeddedVector<char, 100> buf;
|
||||
CHECK_EQ(100, ReadData(&dynabuf, 0, &buf));
|
||||
CHECK_EQ(ref_buf, buf);
|
||||
// Check the seal.
|
||||
EmbeddedVector<char, 50> seal_buf;
|
||||
CHECK_EQ(seal_size, ReadData(&dynabuf, 100, &seal_buf));
|
||||
CHECK_EQ(CStrVector(seal), seal_buf.SubVector(0, seal_size));
|
||||
// Verify that there's no data beyond the seal.
|
||||
CHECK_EQ(0, ReadData(&dynabuf, 100 + seal_size, &buf));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,8 @@
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Load source code files from <project root>/tools.
|
||||
// Files: tools/consarray.js tools/profile.js tools/profile_view.js
|
||||
// Files: tools/codemap.js tools/consarray.js tools/profile.js
|
||||
// Files: tools/profile_view.js
|
||||
|
||||
|
||||
function createNode(name, time, opt_parent) {
|
||||
|
@ -210,6 +210,14 @@ CodeMap.prototype.getAllDynamicEntries = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of pairs of all dynamic code entries and their addresses.
|
||||
*/
|
||||
CodeMap.prototype.getAllDynamicEntriesWithAddresses = function() {
|
||||
return this.dynamics_.exportKeysAndValues();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of all static code entries.
|
||||
*/
|
||||
|
@ -162,8 +162,16 @@ Profile.prototype.addFuncCode = function(
|
||||
// Function object has been overwritten with a new one.
|
||||
func.name = name;
|
||||
}
|
||||
var entry = new Profile.DynamicFuncCodeEntry(size, type, func, state);
|
||||
this.codeMap_.addCode(start, entry);
|
||||
var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
|
||||
if (entry) {
|
||||
if (entry.size === size && entry.func === func) {
|
||||
// Entry state has changed.
|
||||
entry.state = state;
|
||||
}
|
||||
} else {
|
||||
entry = new Profile.DynamicFuncCodeEntry(size, type, func, state);
|
||||
this.codeMap_.addCode(start, entry);
|
||||
}
|
||||
return entry;
|
||||
};
|
||||
|
||||
@ -373,6 +381,31 @@ Profile.prototype.getFlatProfile = function(opt_label) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up function entries that are not referenced by code entries.
|
||||
*/
|
||||
Profile.prototype.cleanUpFuncEntries = function() {
|
||||
var referencedFuncEntries = [];
|
||||
var entries = this.codeMap_.getAllDynamicEntriesWithAddresses();
|
||||
for (var i = 0, l = entries.length; i < l; ++i) {
|
||||
if (entries[i][1].constructor === Profile.FunctionEntry) {
|
||||
entries[i][1].used = false;
|
||||
}
|
||||
}
|
||||
for (var i = 0, l = entries.length; i < l; ++i) {
|
||||
if ("func" in entries[i][1]) {
|
||||
entries[i][1].func.used = true;
|
||||
}
|
||||
}
|
||||
for (var i = 0, l = entries.length; i < l; ++i) {
|
||||
if (entries[i][1].constructor === Profile.FunctionEntry &&
|
||||
!entries[i][1].used) {
|
||||
this.codeMap_.deleteCode(entries[i][0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a dynamic code entry.
|
||||
*
|
||||
@ -408,6 +441,11 @@ Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
|
||||
};
|
||||
|
||||
|
||||
Profile.DynamicCodeEntry.prototype.toString = function() {
|
||||
return this.getName() + ': ' + this.size.toString(16);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a dynamic code entry.
|
||||
*
|
||||
@ -448,6 +486,11 @@ Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
|
||||
};
|
||||
|
||||
|
||||
Profile.DynamicFuncCodeEntry.prototype.toString = function() {
|
||||
return this.getName() + ': ' + this.size.toString(16);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a shared function object entry.
|
||||
*
|
||||
@ -473,6 +516,7 @@ Profile.FunctionEntry.prototype.getName = function() {
|
||||
return name;
|
||||
};
|
||||
|
||||
Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString;
|
||||
|
||||
/**
|
||||
* Constructs a call graph.
|
||||
|
@ -190,6 +190,17 @@ SplayTree.prototype.findGreatestLessThan = function(key) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array<*>} An array containing all the values of tree's nodes paired
|
||||
* with keys.
|
||||
*/
|
||||
SplayTree.prototype.exportKeysAndValues = function() {
|
||||
var result = [];
|
||||
this.traverse_(function(node) { result.push([node.key, node.value]); });
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array<*>} An array containing all the values of tree's nodes.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user