Commit 821c3e56 authored by fmeawad's avatar fmeawad Committed by Commit bot

Reland [Tracing] V8 Tracing Controller

V8 has had a trace event macro interface for while, but without a tracing
controller a standalone V8 would be unable to collect traces.

This CL introduces a complete Tracing Controller system for V8.
It is fully function except that it does not yet store trace event args.

This CL has a few components,
The tracing controller itself, contributed by the author of this CL
The Trace config (including the parser), contributed by lpy@
The Trace Object, Trace Writer, and Trace Buffer are all contributed by rksang@

BUG=v8:4561
LOG=N

The original CL was failing the V8 Arm Builder:
https://build.chromium.org/p/client.v8.ports/builders/V8%20Arm%20-%20builder/builds/2456
and the V8 Mips Builder:
https://build.chromium.org/p/client.v8.ports/builders/V8%20Mips%20-%20builder/builds/2506

The failure is due to undefined behavior of CHECK_EQ of 2 const char*

Fix in patch #1

Committed: https://crrev.com/3d598452679ce208ad9b2f48e0fb3fae352ce375
Cr-Commit-Position: refs/heads/master@{#38073}

patch from issue 2137013006 at patchset 200001 (http://crrev.com/2137013006#ps200001)

Review-Url: https://codereview.chromium.org/2183923004
Cr-Commit-Position: refs/heads/master@{#38104}
parent 928d2395
...@@ -2114,10 +2114,18 @@ v8_source_set("v8_libbase") { ...@@ -2114,10 +2114,18 @@ v8_source_set("v8_libbase") {
v8_source_set("v8_libplatform") { v8_source_set("v8_libplatform") {
sources = [ sources = [
"include/libplatform/libplatform.h", "include/libplatform/libplatform.h",
"include/libplatform/v8-tracing.h",
"src/libplatform/default-platform.cc", "src/libplatform/default-platform.cc",
"src/libplatform/default-platform.h", "src/libplatform/default-platform.h",
"src/libplatform/task-queue.cc", "src/libplatform/task-queue.cc",
"src/libplatform/task-queue.h", "src/libplatform/task-queue.h",
"src/libplatform/tracing/trace-buffer.cc",
"src/libplatform/tracing/trace-buffer.h",
"src/libplatform/tracing/trace-config.cc",
"src/libplatform/tracing/trace-object.cc",
"src/libplatform/tracing/trace-writer.cc",
"src/libplatform/tracing/trace-writer.h",
"src/libplatform/tracing/tracing-controller.cc",
"src/libplatform/worker-thread.cc", "src/libplatform/worker-thread.cc",
"src/libplatform/worker-thread.h", "src/libplatform/worker-thread.h",
] ]
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef V8_LIBPLATFORM_LIBPLATFORM_H_ #ifndef V8_LIBPLATFORM_LIBPLATFORM_H_
#define V8_LIBPLATFORM_LIBPLATFORM_H_ #define V8_LIBPLATFORM_LIBPLATFORM_H_
#include "include/libplatform/v8-tracing.h"
#include "v8-platform.h" // NOLINT(build/include) #include "v8-platform.h" // NOLINT(build/include)
namespace v8 { namespace v8 {
...@@ -31,6 +32,14 @@ v8::Platform* CreateDefaultPlatform(int thread_pool_size = 0); ...@@ -31,6 +32,14 @@ v8::Platform* CreateDefaultPlatform(int thread_pool_size = 0);
*/ */
bool PumpMessageLoop(v8::Platform* platform, v8::Isolate* isolate); bool PumpMessageLoop(v8::Platform* platform, v8::Isolate* isolate);
/**
* Attempts to set the tracing controller for the given platform.
*
* The |platform| has to be created using |CreateDefaultPlatform|.
*/
void SetTracingController(
v8::Platform* platform,
v8::platform::tracing::TracingController* tracing_controller);
} // namespace platform } // namespace platform
} // namespace v8 } // namespace v8
......
// Copyright 2016 the V8 project 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 V8_LIBPLATFORM_V8_TRACING_H_
#define V8_LIBPLATFORM_V8_TRACING_H_
#include <fstream>
#include <memory>
#include <vector>
namespace v8 {
namespace platform {
namespace tracing {
class TraceObject {
public:
TraceObject() {}
void Initialize(char phase, const uint8_t* category_enabled_flag,
const char* name, const char* scope, uint64_t id,
uint64_t bind_id, int num_args, const char** arg_names,
const uint8_t* arg_types, const uint64_t* arg_values,
unsigned int flags);
void UpdateDuration();
void InitializeForTesting(char phase, const uint8_t* category_enabled_flag,
const char* name, const char* scope, uint64_t id,
uint64_t bind_id, int num_args,
const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values, unsigned int flags,
int pid, int tid, int64_t ts, int64_t tts,
uint64_t duration, uint64_t cpu_duration);
int pid() const { return pid_; }
int tid() const { return tid_; }
char phase() const { return phase_; }
const uint8_t* category_enabled_flag() const {
return category_enabled_flag_;
}
const char* name() const { return name_; }
const char* scope() const { return scope_; }
uint64_t id() const { return id_; }
uint64_t bind_id() const { return bind_id_; }
unsigned int flags() const { return flags_; }
int64_t ts() { return ts_; }
int64_t tts() { return tts_; }
uint64_t duration() { return duration_; }
uint64_t cpu_duration() { return cpu_duration_; }
private:
int pid_;
int tid_;
char phase_;
const char* name_;
const char* scope_;
const uint8_t* category_enabled_flag_;
uint64_t id_;
uint64_t bind_id_;
int num_args_;
unsigned int flags_;
int64_t ts_;
int64_t tts_;
uint64_t duration_;
uint64_t cpu_duration_;
// TODO(fmeawad): Add args support.
// Disallow copy and assign
TraceObject(const TraceObject&) = delete;
void operator=(const TraceObject&) = delete;
};
class TraceWriter {
public:
TraceWriter() {}
virtual ~TraceWriter() {}
virtual void AppendTraceEvent(TraceObject* trace_event) = 0;
virtual void Flush() = 0;
static TraceWriter* CreateJSONTraceWriter(std::ostream& stream);
private:
// Disallow copy and assign
TraceWriter(const TraceWriter&) = delete;
void operator=(const TraceWriter&) = delete;
};
class TraceBufferChunk {
public:
explicit TraceBufferChunk(uint32_t seq);
void Reset(uint32_t new_seq);
bool IsFull() const { return next_free_ == kChunkSize; }
TraceObject* AddTraceEvent(size_t* event_index);
TraceObject* GetEventAt(size_t index) { return &chunk_[index]; }
uint32_t seq() const { return seq_; }
size_t size() const { return next_free_; }
static const size_t kChunkSize = 64;
private:
size_t next_free_ = 0;
TraceObject chunk_[kChunkSize];
uint32_t seq_;
// Disallow copy and assign
TraceBufferChunk(const TraceBufferChunk&) = delete;
void operator=(const TraceBufferChunk&) = delete;
};
class TraceBuffer {
public:
TraceBuffer() {}
virtual ~TraceBuffer() {}
virtual TraceObject* AddTraceEvent(uint64_t* handle) = 0;
virtual TraceObject* GetEventByHandle(uint64_t handle) = 0;
virtual bool Flush() = 0;
static const size_t kRingBufferChunks = 1024;
static TraceBuffer* CreateTraceBufferRingBuffer(size_t max_chunks,
TraceWriter* trace_writer);
private:
// Disallow copy and assign
TraceBuffer(const TraceBuffer&) = delete;
void operator=(const TraceBuffer&) = delete;
};
// Options determines how the trace buffer stores data.
enum TraceRecordMode {
// Record until the trace buffer is full.
RECORD_UNTIL_FULL,
// Record until the user ends the trace. The trace buffer is a fixed size
// and we use it as a ring buffer during recording.
RECORD_CONTINUOUSLY,
// Record until the trace buffer is full, but with a huge buffer size.
RECORD_AS_MUCH_AS_POSSIBLE,
// Echo to console. Events are discarded.
ECHO_TO_CONSOLE,
};
class TraceConfig {
public:
typedef std::vector<std::string> StringList;
TraceConfig()
: enable_sampling_(false),
enable_systrace_(false),
enable_argument_filter_(false) {}
TraceRecordMode GetTraceRecordMode() const { return record_mode_; }
bool IsSamplingEnabled() const { return enable_sampling_; }
bool IsSystraceEnabled() const { return enable_systrace_; }
bool IsArgumentFilterEnabled() const { return enable_argument_filter_; }
void SetTraceRecordMode(TraceRecordMode mode) { record_mode_ = mode; }
void EnableSampling() { enable_sampling_ = true; }
void EnableSystrace() { enable_systrace_ = true; }
void EnableArgumentFilter() { enable_argument_filter_ = true; }
void AddIncludedCategory(const char* included_category);
void AddExcludedCategory(const char* excluded_category);
bool IsCategoryGroupEnabled(const char* category_group) const;
private:
TraceRecordMode record_mode_;
bool enable_sampling_ : 1;
bool enable_systrace_ : 1;
bool enable_argument_filter_ : 1;
StringList included_categories_;
StringList excluded_categories_;
// Disallow copy and assign
TraceConfig(const TraceConfig&) = delete;
void operator=(const TraceConfig&) = delete;
};
class TracingController {
public:
enum Mode { DISABLED = 0, RECORDING_MODE };
// The pointer returned from GetCategoryGroupEnabledInternal() points to a
// value with zero or more of the following bits. Used in this class only.
// The TRACE_EVENT macros should only use the value as a bool.
// These values must be in sync with macro values in TraceEvent.h in Blink.
enum CategoryGroupEnabledFlags {
// Category group enabled for the recording mode.
ENABLED_FOR_RECORDING = 1 << 0,
// Category group enabled by SetEventCallbackEnabled().
ENABLED_FOR_EVENT_CALLBACK = 1 << 2,
// Category group enabled to export events to ETW.
ENABLED_FOR_ETW_EXPORT = 1 << 3
};
TracingController() {}
void Initialize(TraceBuffer* trace_buffer);
const uint8_t* GetCategoryGroupEnabled(const char* category_group);
static const char* GetCategoryGroupName(const uint8_t* category_enabled_flag);
uint64_t AddTraceEvent(char phase, const uint8_t* category_enabled_flag,
const char* name, const char* scope, uint64_t id,
uint64_t bind_id, int32_t num_args,
const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values, unsigned int flags);
void UpdateTraceEventDuration(const uint8_t* category_enabled_flag,
const char* name, uint64_t handle);
void StartTracing(TraceConfig* trace_config);
void StopTracing();
private:
const uint8_t* GetCategoryGroupEnabledInternal(const char* category_group);
void UpdateCategoryGroupEnabledFlag(size_t category_index);
void UpdateCategoryGroupEnabledFlags();
std::unique_ptr<TraceBuffer> trace_buffer_;
std::unique_ptr<TraceConfig> trace_config_;
Mode mode_ = DISABLED;
// Disallow copy and assign
TracingController(const TracingController&) = delete;
void operator=(const TracingController&) = delete;
};
} // namespace tracing
} // namespace platform
} // namespace v8
#endif // V8_LIBPLATFORM_V8_TRACING_H_
...@@ -22,5 +22,6 @@ include_rules = [ ...@@ -22,5 +22,6 @@ include_rules = [
specific_include_rules = { specific_include_rules = {
"d8\.cc": [ "d8\.cc": [
"+include/libplatform/libplatform.h", "+include/libplatform/libplatform.h",
"+include/libplatform/v8-tracing.h",
], ],
} }
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "src/ostreams.h" #include "src/ostreams.h"
#include "include/libplatform/libplatform.h" #include "include/libplatform/libplatform.h"
#include "include/libplatform/v8-tracing.h"
#ifndef V8_SHARED #ifndef V8_SHARED
#include "src/api.h" #include "src/api.h"
#include "src/base/cpu.h" #include "src/base/cpu.h"
...@@ -1991,6 +1992,9 @@ bool Shell::SetOptions(int argc, char* argv[]) { ...@@ -1991,6 +1992,9 @@ bool Shell::SetOptions(int argc, char* argv[]) {
return false; return false;
} }
argv[i] = NULL; argv[i] = NULL;
} else if (strcmp(argv[i], "--enable-tracing") == 0) {
options.trace_enabled = true;
argv[i] = NULL;
} }
} }
...@@ -2407,6 +2411,7 @@ static void DumpHeapConstants(i::Isolate* isolate) { ...@@ -2407,6 +2411,7 @@ static void DumpHeapConstants(i::Isolate* isolate) {
int Shell::Main(int argc, char* argv[]) { int Shell::Main(int argc, char* argv[]) {
std::ofstream trace_file;
#if (defined(_WIN32) || defined(_WIN64)) #if (defined(_WIN32) || defined(_WIN64))
UINT new_flags = UINT new_flags =
SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX; SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
...@@ -2474,6 +2479,29 @@ int Shell::Main(int argc, char* argv[]) { ...@@ -2474,6 +2479,29 @@ int Shell::Main(int argc, char* argv[]) {
Initialize(isolate); Initialize(isolate);
PerIsolateData data(isolate); PerIsolateData data(isolate);
if (options.trace_enabled) {
trace_file.open("v8_trace.json");
platform::tracing::TracingController* tracing_controller =
new platform::tracing::TracingController();
platform::tracing::TraceBuffer* trace_buffer =
platform::tracing::TraceBuffer::CreateTraceBufferRingBuffer(
platform::tracing::TraceBuffer::kRingBufferChunks,
platform::tracing::TraceWriter::CreateJSONTraceWriter(
trace_file));
platform::tracing::TraceConfig* trace_config;
trace_config = new platform::tracing::TraceConfig();
trace_config->AddIncludedCategory("v8");
tracing_controller->Initialize(trace_buffer);
tracing_controller->StartTracing(trace_config);
#ifndef V8_SHARED
if (!i::FLAG_verify_predictable) {
platform::SetTracingController(g_platform, tracing_controller);
}
#else
platform::SetTracingController(g_platform, tracing_controller);
#endif
}
#ifndef V8_SHARED #ifndef V8_SHARED
if (options.dump_heap_constants) { if (options.dump_heap_constants) {
DumpHeapConstants(reinterpret_cast<i::Isolate*>(isolate)); DumpHeapConstants(reinterpret_cast<i::Isolate*>(isolate));
......
...@@ -290,7 +290,8 @@ class ShellOptions { ...@@ -290,7 +290,8 @@ class ShellOptions {
isolate_sources(NULL), isolate_sources(NULL),
icu_data_file(NULL), icu_data_file(NULL),
natives_blob(NULL), natives_blob(NULL),
snapshot_blob(NULL) {} snapshot_blob(NULL),
trace_enabled(false) {}
~ShellOptions() { ~ShellOptions() {
delete[] isolate_sources; delete[] isolate_sources;
...@@ -318,6 +319,8 @@ class ShellOptions { ...@@ -318,6 +319,8 @@ class ShellOptions {
const char* icu_data_file; const char* icu_data_file;
const char* natives_blob; const char* natives_blob;
const char* snapshot_blob; const char* snapshot_blob;
bool trace_enabled;
const char* trace_config;
}; };
#ifdef V8_SHARED #ifdef V8_SHARED
......
...@@ -29,11 +29,17 @@ bool PumpMessageLoop(v8::Platform* platform, v8::Isolate* isolate) { ...@@ -29,11 +29,17 @@ bool PumpMessageLoop(v8::Platform* platform, v8::Isolate* isolate) {
return reinterpret_cast<DefaultPlatform*>(platform)->PumpMessageLoop(isolate); return reinterpret_cast<DefaultPlatform*>(platform)->PumpMessageLoop(isolate);
} }
void SetTracingController(
v8::Platform* platform,
v8::platform::tracing::TracingController* tracing_controller) {
return reinterpret_cast<DefaultPlatform*>(platform)->SetTracingController(
tracing_controller);
}
const int DefaultPlatform::kMaxThreadPoolSize = 8; const int DefaultPlatform::kMaxThreadPoolSize = 8;
DefaultPlatform::DefaultPlatform() DefaultPlatform::DefaultPlatform()
: initialized_(false), thread_pool_size_(0) {} : initialized_(false), thread_pool_size_(0), tracing_controller_(NULL) {}
DefaultPlatform::~DefaultPlatform() { DefaultPlatform::~DefaultPlatform() {
base::LockGuard<base::Mutex> guard(&lock_); base::LockGuard<base::Mutex> guard(&lock_);
...@@ -57,6 +63,11 @@ DefaultPlatform::~DefaultPlatform() { ...@@ -57,6 +63,11 @@ DefaultPlatform::~DefaultPlatform() {
i->second.pop(); i->second.pop();
} }
} }
if (tracing_controller_) {
tracing_controller_->StopTracing();
delete tracing_controller_;
}
} }
...@@ -173,15 +184,27 @@ uint64_t DefaultPlatform::AddTraceEvent( ...@@ -173,15 +184,27 @@ uint64_t DefaultPlatform::AddTraceEvent(
const char* scope, uint64_t id, uint64_t bind_id, int num_args, const char* scope, uint64_t id, uint64_t bind_id, int num_args,
const char** arg_names, const uint8_t* arg_types, const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values, unsigned int flags) { const uint64_t* arg_values, unsigned int flags) {
if (tracing_controller_) {
return tracing_controller_->AddTraceEvent(
phase, category_enabled_flag, name, scope, id, bind_id, num_args,
arg_names, arg_types, arg_values, flags);
}
return 0; return 0;
} }
void DefaultPlatform::UpdateTraceEventDuration( void DefaultPlatform::UpdateTraceEventDuration(
const uint8_t* category_enabled_flag, const char* name, uint64_t handle) {} const uint8_t* category_enabled_flag, const char* name, uint64_t handle) {
if (tracing_controller_) {
tracing_controller_->UpdateTraceEventDuration(category_enabled_flag, name,
handle);
}
}
const uint8_t* DefaultPlatform::GetCategoryGroupEnabled(const char* name) { const uint8_t* DefaultPlatform::GetCategoryGroupEnabled(const char* name) {
if (tracing_controller_) {
return tracing_controller_->GetCategoryGroupEnabled(name);
}
static uint8_t no = 0; static uint8_t no = 0;
return &no; return &no;
} }
...@@ -193,6 +216,10 @@ const char* DefaultPlatform::GetCategoryGroupName( ...@@ -193,6 +216,10 @@ const char* DefaultPlatform::GetCategoryGroupName(
return dummy; return dummy;
} }
void DefaultPlatform::SetTracingController(
tracing::TracingController* tracing_controller) {
tracing_controller_ = tracing_controller;
}
size_t DefaultPlatform::NumberOfAvailableBackgroundThreads() { size_t DefaultPlatform::NumberOfAvailableBackgroundThreads() {
return static_cast<size_t>(thread_pool_size_); return static_cast<size_t>(thread_pool_size_);
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <queue> #include <queue>
#include <vector> #include <vector>
#include "include/libplatform/v8-tracing.h"
#include "include/v8-platform.h" #include "include/v8-platform.h"
#include "src/base/macros.h" #include "src/base/macros.h"
#include "src/base/platform/mutex.h" #include "src/base/platform/mutex.h"
...@@ -22,6 +23,10 @@ class TaskQueue; ...@@ -22,6 +23,10 @@ class TaskQueue;
class Thread; class Thread;
class WorkerThread; class WorkerThread;
namespace tracing {
class TracingController;
}
class DefaultPlatform : public Platform { class DefaultPlatform : public Platform {
public: public:
DefaultPlatform(); DefaultPlatform();
...@@ -54,7 +59,7 @@ class DefaultPlatform : public Platform { ...@@ -54,7 +59,7 @@ class DefaultPlatform : public Platform {
unsigned int flags) override; unsigned int flags) override;
void UpdateTraceEventDuration(const uint8_t* category_enabled_flag, void UpdateTraceEventDuration(const uint8_t* category_enabled_flag,
const char* name, uint64_t handle) override; const char* name, uint64_t handle) override;
void SetTracingController(tracing::TracingController* tracing_controller);
private: private:
static const int kMaxThreadPoolSize; static const int kMaxThreadPoolSize;
...@@ -74,6 +79,7 @@ class DefaultPlatform : public Platform { ...@@ -74,6 +79,7 @@ class DefaultPlatform : public Platform {
std::priority_queue<DelayedEntry, std::vector<DelayedEntry>, std::priority_queue<DelayedEntry, std::vector<DelayedEntry>,
std::greater<DelayedEntry> > > std::greater<DelayedEntry> > >
main_thread_delayed_queue_; main_thread_delayed_queue_;
tracing::TracingController* tracing_controller_;
DISALLOW_COPY_AND_ASSIGN(DefaultPlatform); DISALLOW_COPY_AND_ASSIGN(DefaultPlatform);
}; };
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/libplatform/tracing/trace-buffer.h"
namespace v8 {
namespace platform {
namespace tracing {
TraceBufferRingBuffer::TraceBufferRingBuffer(size_t max_chunks,
TraceWriter* trace_writer)
: max_chunks_(max_chunks) {
trace_writer_.reset(trace_writer);
chunks_.resize(max_chunks);
}
TraceBufferRingBuffer::~TraceBufferRingBuffer() {}
TraceObject* TraceBufferRingBuffer::AddTraceEvent(uint64_t* handle) {
base::LockGuard<base::Mutex> guard(&mutex_);
if (is_empty_ || chunks_[chunk_index_]->IsFull()) {
chunk_index_ = is_empty_ ? 0 : NextChunkIndex(chunk_index_);
is_empty_ = false;
auto& chunk = chunks_[chunk_index_];
if (chunk) {
chunk->Reset(current_chunk_seq_++);
} else {
chunk.reset(new TraceBufferChunk(current_chunk_seq_++));
}
}
auto& chunk = chunks_[chunk_index_];
size_t event_index;
TraceObject* trace_object = chunk->AddTraceEvent(&event_index);
*handle = MakeHandle(chunk_index_, chunk->seq(), event_index);
return trace_object;
}
TraceObject* TraceBufferRingBuffer::GetEventByHandle(uint64_t handle) {
base::LockGuard<base::Mutex> guard(&mutex_);
size_t chunk_index, event_index;
uint32_t chunk_seq;
ExtractHandle(handle, &chunk_index, &chunk_seq, &event_index);
if (chunk_index >= chunks_.size()) return NULL;
auto& chunk = chunks_[chunk_index];
if (!chunk || chunk->seq() != chunk_seq) return NULL;
return chunk->GetEventAt(event_index);
}
bool TraceBufferRingBuffer::Flush() {
base::LockGuard<base::Mutex> guard(&mutex_);
// This flushes all the traces stored in the buffer.
if (!is_empty_) {
for (size_t i = NextChunkIndex(chunk_index_);; i = NextChunkIndex(i)) {
if (auto& chunk = chunks_[i]) {
for (size_t j = 0; j < chunk->size(); ++j) {
trace_writer_->AppendTraceEvent(chunk->GetEventAt(j));
}
}
if (i == chunk_index_) break;
}
}
trace_writer_->Flush();
// This resets the trace buffer.
is_empty_ = true;
return true;
}
uint64_t TraceBufferRingBuffer::MakeHandle(size_t chunk_index,
uint32_t chunk_seq,
size_t event_index) const {
return static_cast<uint64_t>(chunk_seq) * Capacity() +
chunk_index * TraceBufferChunk::kChunkSize + event_index;
}
void TraceBufferRingBuffer::ExtractHandle(uint64_t handle, size_t* chunk_index,
uint32_t* chunk_seq,
size_t* event_index) const {
*chunk_seq = static_cast<uint32_t>(handle / Capacity());
size_t indices = handle % Capacity();
*chunk_index = indices / TraceBufferChunk::kChunkSize;
*event_index = indices % TraceBufferChunk::kChunkSize;
}
size_t TraceBufferRingBuffer::NextChunkIndex(size_t index) const {
if (++index >= max_chunks_) index = 0;
return index;
}
TraceBufferChunk::TraceBufferChunk(uint32_t seq) : seq_(seq) {}
void TraceBufferChunk::Reset(uint32_t new_seq) {
next_free_ = 0;
seq_ = new_seq;
}
TraceObject* TraceBufferChunk::AddTraceEvent(size_t* event_index) {
*event_index = next_free_++;
return &chunk_[*event_index];
}
TraceBuffer* TraceBuffer::CreateTraceBufferRingBuffer(
size_t max_chunks, TraceWriter* trace_writer) {
return new TraceBufferRingBuffer(max_chunks, trace_writer);
}
} // namespace tracing
} // namespace platform
} // namespace v8
// Copyright 2016 the V8 project 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 SRC_LIBPLATFORM_TRACING_TRACE_BUFFER_H_
#define SRC_LIBPLATFORM_TRACING_TRACE_BUFFER_H_
#include <memory>
#include <vector>
#include "include/libplatform/v8-tracing.h"
#include "src/base/platform/mutex.h"
namespace v8 {
namespace platform {
namespace tracing {
class TraceBufferRingBuffer : public TraceBuffer {
public:
TraceBufferRingBuffer(size_t max_chunks, TraceWriter* trace_writer);
~TraceBufferRingBuffer();
TraceObject* AddTraceEvent(uint64_t* handle) override;
TraceObject* GetEventByHandle(uint64_t handle) override;
bool Flush() override;
private:
uint64_t MakeHandle(size_t chunk_index, uint32_t chunk_seq,
size_t event_index) const;
void ExtractHandle(uint64_t handle, size_t* chunk_index, uint32_t* chunk_seq,
size_t* event_index) const;
size_t Capacity() const { return max_chunks_ * TraceBufferChunk::kChunkSize; }
size_t NextChunkIndex(size_t index) const;
mutable base::Mutex mutex_;
size_t max_chunks_;
std::unique_ptr<TraceWriter> trace_writer_;
std::vector<std::unique_ptr<TraceBufferChunk>> chunks_;
size_t chunk_index_;
bool is_empty_ = true;
uint32_t current_chunk_seq_ = 1;
};
} // namespace tracing
} // namespace platform
} // namespace v8
#endif // SRC_LIBPLATFORM_TRACING_TRACE_BUFFER_H_
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string.h>
#include "include/libplatform/v8-tracing.h"
#include "src/base/logging.h"
namespace v8 {
class Isolate;
namespace platform {
namespace tracing {
bool TraceConfig::IsCategoryGroupEnabled(const char* category_group) const {
for (auto included_category : included_categories_) {
if (strcmp(included_category.data(), category_group) == 0) return true;
}
return false;
}
void TraceConfig::AddIncludedCategory(const char* included_category) {
DCHECK(included_category != NULL && strlen(included_category) > 0);
included_categories_.push_back(included_category);
}
void TraceConfig::AddExcludedCategory(const char* excluded_category) {
DCHECK(excluded_category != NULL && strlen(excluded_category) > 0);
excluded_categories_.push_back(excluded_category);
}
} // namespace tracing
} // namespace platform
} // namespace v8
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/libplatform/v8-tracing.h"
#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
namespace v8 {
namespace platform {
namespace tracing {
void TraceObject::Initialize(char phase, const uint8_t* category_enabled_flag,
const char* name, const char* scope, uint64_t id,
uint64_t bind_id, int num_args,
const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values, unsigned int flags) {
pid_ = base::OS::GetCurrentProcessId();
tid_ = base::OS::GetCurrentThreadId();
phase_ = phase;
category_enabled_flag_ = category_enabled_flag;
name_ = name;
scope_ = scope;
id_ = id;
bind_id_ = bind_id;
num_args_ = num_args;
flags_ = flags;
ts_ = base::TimeTicks::HighResolutionNow().ToInternalValue();
tts_ = base::ThreadTicks::Now().ToInternalValue();
duration_ = 0;
cpu_duration_ = 0;
}
void TraceObject::UpdateDuration() {
duration_ = base::TimeTicks::HighResolutionNow().ToInternalValue() - ts_;
cpu_duration_ = base::ThreadTicks::Now().ToInternalValue() - tts_;
}
void TraceObject::InitializeForTesting(
char phase, const uint8_t* category_enabled_flag, const char* name,
const char* scope, uint64_t id, uint64_t bind_id, int num_args,
const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values, unsigned int flags, int pid, int tid,
int64_t ts, int64_t tts, uint64_t duration, uint64_t cpu_duration) {
pid_ = pid;
tid_ = tid;
phase_ = phase;
category_enabled_flag_ = category_enabled_flag;
name_ = name;
scope_ = scope;
id_ = id;
bind_id_ = bind_id;
num_args_ = num_args;
flags_ = flags;
ts_ = ts;
tts_ = tts;
duration_ = duration;
cpu_duration_ = cpu_duration;
}
} // namespace tracing
} // namespace platform
} // namespace v8
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/libplatform/tracing/trace-writer.h"
#include "src/base/platform/platform.h"
namespace v8 {
namespace platform {
namespace tracing {
JSONTraceWriter::JSONTraceWriter(std::ostream& stream) : stream_(stream) {
stream_ << "{\"traceEvents\":[";
}
JSONTraceWriter::~JSONTraceWriter() { stream_ << "]}"; }
void JSONTraceWriter::AppendTraceEvent(TraceObject* trace_event) {
if (append_comma_) stream_ << ",";
append_comma_ = true;
if (trace_event->scope() == NULL) {
stream_ << "{\"pid\":" << trace_event->pid()
<< ",\"tid\":" << trace_event->tid()
<< ",\"ts\":" << trace_event->ts()
<< ",\"tts\":" << trace_event->tts() << ",\"ph\":\""
<< trace_event->phase() << "\",\"cat\":\""
<< TracingController::GetCategoryGroupName(
trace_event->category_enabled_flag())
<< "\",\"name\":\"" << trace_event->name()
<< "\",\"args\":{},\"dur\":" << trace_event->duration()
<< ",\"tdur\":" << trace_event->cpu_duration() << "}";
} else {
stream_ << "{\"pid\":" << trace_event->pid()
<< ",\"tid\":" << trace_event->tid()
<< ",\"ts\":" << trace_event->ts()
<< ",\"tts\":" << trace_event->tts() << ",\"ph\":\""
<< trace_event->phase() << "\",\"cat\":\""
<< TracingController::GetCategoryGroupName(
trace_event->category_enabled_flag())
<< "\",\"name\":\"" << trace_event->name() << "\",\"scope\":\""
<< trace_event->scope()
<< "\",\"args\":{},\"dur\":" << trace_event->duration()
<< ",\"tdur\":" << trace_event->cpu_duration() << "}";
}
// TODO(fmeawad): Add support for Flow Events.
}
void JSONTraceWriter::Flush() {}
TraceWriter* TraceWriter::CreateJSONTraceWriter(std::ostream& stream) {
return new JSONTraceWriter(stream);
}
} // namespace tracing
} // namespace platform
} // namespace v8
// Copyright 2016 the V8 project 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 SRC_LIBPLATFORM_TRACING_TRACE_WRITER_H_
#define SRC_LIBPLATFORM_TRACING_TRACE_WRITER_H_
#include "include/libplatform/v8-tracing.h"
namespace v8 {
namespace platform {
namespace tracing {
class JSONTraceWriter : public TraceWriter {
public:
explicit JSONTraceWriter(std::ostream& stream);
~JSONTraceWriter();
void AppendTraceEvent(TraceObject* trace_event) override;
void Flush() override;
private:
std::ostream& stream_;
bool append_comma_ = false;
};
} // namespace tracing
} // namespace platform
} // namespace v8
#endif // SRC_LIBPLATFORM_TRACING_TRACE_WRITER_H_
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include <string.h>
#include "include/libplatform/v8-tracing.h"
#include "src/base/platform/mutex.h"
namespace v8 {
namespace platform {
namespace tracing {
#define MAX_CATEGORY_GROUPS 200
// Parallel arrays g_category_groups and g_category_group_enabled are separate
// so that a pointer to a member of g_category_group_enabled can be easily
// converted to an index into g_category_groups. This allows macros to deal
// only with char enabled pointers from g_category_group_enabled, and we can
// convert internally to determine the category name from the char enabled
// pointer.
const char* g_category_groups[MAX_CATEGORY_GROUPS] = {
"toplevel", "tracing already shutdown",
"tracing categories exhausted; must increase MAX_CATEGORY_GROUPS",
"__metadata"};
// The enabled flag is char instead of bool so that the API can be used from C.
unsigned char g_category_group_enabled[MAX_CATEGORY_GROUPS] = {0};
// Indexes here have to match the g_category_groups array indexes above.
const int g_category_already_shutdown = 1;
const int g_category_categories_exhausted = 2;
// Metadata category not used in V8.
// const int g_category_metadata = 3;
const int g_num_builtin_categories = 4;
// Skip default categories.
v8::base::AtomicWord g_category_index = g_num_builtin_categories;
void TracingController::Initialize(TraceBuffer* trace_buffer) {
trace_buffer_.reset(trace_buffer);
}
uint64_t TracingController::AddTraceEvent(
char phase, const uint8_t* category_enabled_flag, const char* name,
const char* scope, uint64_t id, uint64_t bind_id, int num_args,
const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values, unsigned int flags) {
uint64_t handle;
TraceObject* trace_object = trace_buffer_->AddTraceEvent(&handle);
if (trace_object) {
trace_object->Initialize(phase, category_enabled_flag, name, scope, id,
bind_id, num_args, arg_names, arg_types,
arg_values, flags);
}
return handle;
}
void TracingController::UpdateTraceEventDuration(
const uint8_t* category_enabled_flag, const char* name, uint64_t handle) {
TraceObject* trace_object = trace_buffer_->GetEventByHandle(handle);
if (!trace_object) return;
trace_object->UpdateDuration();
}
const uint8_t* TracingController::GetCategoryGroupEnabled(
const char* category_group) {
if (!trace_buffer_) {
DCHECK(!g_category_group_enabled[g_category_already_shutdown]);
return &g_category_group_enabled[g_category_already_shutdown];
}
return GetCategoryGroupEnabledInternal(category_group);
}
const char* TracingController::GetCategoryGroupName(
const uint8_t* category_group_enabled) {
// Calculate the index of the category group by finding
// category_group_enabled in g_category_group_enabled array.
uintptr_t category_begin =
reinterpret_cast<uintptr_t>(g_category_group_enabled);
uintptr_t category_ptr = reinterpret_cast<uintptr_t>(category_group_enabled);
// Check for out of bounds category pointers.
DCHECK(category_ptr >= category_begin &&
category_ptr < reinterpret_cast<uintptr_t>(g_category_group_enabled +
MAX_CATEGORY_GROUPS));
uintptr_t category_index =
(category_ptr - category_begin) / sizeof(g_category_group_enabled[0]);
return g_category_groups[category_index];
}
void TracingController::StartTracing(TraceConfig* trace_config) {
trace_config_.reset(trace_config);
mode_ = RECORDING_MODE;
UpdateCategoryGroupEnabledFlags();
}
void TracingController::StopTracing() {
mode_ = DISABLED;
UpdateCategoryGroupEnabledFlags();
trace_buffer_->Flush();
}
void TracingController::UpdateCategoryGroupEnabledFlag(size_t category_index) {
unsigned char enabled_flag = 0;
const char* category_group = g_category_groups[category_index];
if (mode_ == RECORDING_MODE &&
trace_config_->IsCategoryGroupEnabled(category_group)) {
enabled_flag |= ENABLED_FOR_RECORDING;
}
// TODO(fmeawad): EventCallback and ETW modes are not yet supported in V8.
// TODO(primiano): this is a temporary workaround for catapult:#2341,
// to guarantee that metadata events are always added even if the category
// filter is "-*". See crbug.com/618054 for more details and long-term fix.
if (mode_ == RECORDING_MODE && !strcmp(category_group, "__metadata")) {
enabled_flag |= ENABLED_FOR_RECORDING;
}
g_category_group_enabled[category_index] = enabled_flag;
}
void TracingController::UpdateCategoryGroupEnabledFlags() {
size_t category_index = base::NoBarrier_Load(&g_category_index);
for (size_t i = 0; i < category_index; i++) UpdateCategoryGroupEnabledFlag(i);
}
const uint8_t* TracingController::GetCategoryGroupEnabledInternal(
const char* category_group) {
// Check that category groups does not contain double quote
DCHECK(!strchr(category_group, '"'));
// The g_category_groups is append only, avoid using a lock for the fast path.
size_t current_category_index = v8::base::Acquire_Load(&g_category_index);
// Search for pre-existing category group.
for (size_t i = 0; i < current_category_index; ++i) {
if (strcmp(g_category_groups[i], category_group) == 0) {
return &g_category_group_enabled[i];
}
}
unsigned char* category_group_enabled = NULL;
size_t category_index = base::Acquire_Load(&g_category_index);
for (size_t i = 0; i < category_index; ++i) {
if (strcmp(g_category_groups[i], category_group) == 0) {
return &g_category_group_enabled[i];
}
}
// Create a new category group.
// Check that there is a slot for the new category_group.
DCHECK(category_index < MAX_CATEGORY_GROUPS);
if (category_index < MAX_CATEGORY_GROUPS) {
// Don't hold on to the category_group pointer, so that we can create
// category groups with strings not known at compile time (this is
// required by SetWatchEvent).
const char* new_group = strdup(category_group);
g_category_groups[category_index] = new_group;
DCHECK(!g_category_group_enabled[category_index]);
// Note that if both included and excluded patterns in the
// TraceConfig are empty, we exclude nothing,
// thereby enabling this category group.
UpdateCategoryGroupEnabledFlag(category_index);
category_group_enabled = &g_category_group_enabled[category_index];
// Update the max index now.
base::Release_Store(&g_category_index, category_index + 1);
} else {
category_group_enabled =
&g_category_group_enabled[g_category_categories_exhausted];
}
return category_group_enabled;
}
} // namespace tracing
} // namespace platform
} // namespace v8
...@@ -2004,10 +2004,18 @@ ...@@ -2004,10 +2004,18 @@
], ],
'sources': [ 'sources': [
'../include/libplatform/libplatform.h', '../include/libplatform/libplatform.h',
'../include/libplatform/v8-tracing.h',
'libplatform/default-platform.cc', 'libplatform/default-platform.cc',
'libplatform/default-platform.h', 'libplatform/default-platform.h',
'libplatform/task-queue.cc', 'libplatform/task-queue.cc',
'libplatform/task-queue.h', 'libplatform/task-queue.h',
'libplatform/tracing/trace-buffer.cc',
'libplatform/tracing/trace-buffer.h',
'libplatform/tracing/trace-config.cc',
'libplatform/tracing/trace-object.cc',
'libplatform/tracing/trace-writer.cc',
'libplatform/tracing/trace-writer.h',
'libplatform/tracing/tracing-controller.cc',
'libplatform/worker-thread.cc', 'libplatform/worker-thread.cc',
'libplatform/worker-thread.h', 'libplatform/worker-thread.h',
], ],
......
...@@ -102,6 +102,7 @@ ...@@ -102,6 +102,7 @@
'heap/test-mark-compact.cc', 'heap/test-mark-compact.cc',
'heap/test-page-promotion.cc', 'heap/test-page-promotion.cc',
'heap/test-spaces.cc', 'heap/test-spaces.cc',
'libplatform/test-tracing.cc',
'libsampler/test-sampler.cc', 'libsampler/test-sampler.cc',
'print-extension.cc', 'print-extension.cc',
'profiler-extension.cc', 'profiler-extension.cc',
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include "include/libplatform/v8-tracing.h"
#include "src/tracing/trace-event.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace platform {
namespace tracing {
TEST(TestTraceConfig) {
LocalContext env;
TraceConfig* trace_config = new TraceConfig();
trace_config->EnableSampling();
trace_config->AddIncludedCategory("v8");
trace_config->AddIncludedCategory(TRACE_DISABLED_BY_DEFAULT("v8.runtime"));
trace_config->AddExcludedCategory("v8.cpu_profile");
CHECK_EQ(trace_config->IsSamplingEnabled(), true);
CHECK_EQ(trace_config->IsSystraceEnabled(), false);
CHECK_EQ(trace_config->IsArgumentFilterEnabled(), false);
CHECK_EQ(trace_config->IsCategoryGroupEnabled("v8"), true);
CHECK_EQ(trace_config->IsCategoryGroupEnabled("v8.cpu_profile"), false);
CHECK_EQ(trace_config->IsCategoryGroupEnabled("v8.cpu_profile.hires"), false);
CHECK_EQ(trace_config->IsCategoryGroupEnabled(
TRACE_DISABLED_BY_DEFAULT("v8.runtime")),
true);
delete trace_config;
}
TEST(TestTraceObject) {
TraceObject trace_object;
uint8_t category_enabled_flag = 41;
trace_object.Initialize('X', &category_enabled_flag, "Test.Trace",
"Test.Scope", 42, 123, 0, NULL, NULL, NULL, 0);
CHECK_EQ('X', trace_object.phase());
CHECK_EQ(category_enabled_flag, *trace_object.category_enabled_flag());
CHECK_EQ(std::string("Test.Trace"), std::string(trace_object.name()));
CHECK_EQ(std::string("Test.Scope"), std::string(trace_object.scope()));
CHECK_EQ(0, trace_object.duration());
CHECK_EQ(0, trace_object.cpu_duration());
}
class MockTraceWriter : public TraceWriter {
public:
void AppendTraceEvent(TraceObject* trace_event) override {
events_.push_back(trace_event->name());
}
void Flush() override {}
std::vector<std::string> events() { return events_; }
private:
std::vector<std::string> events_;
};
TEST(TestTraceBufferRingBuffer) {
// We should be able to add kChunkSize * 2 + 1 trace events.
const int HANDLES_COUNT = TraceBufferChunk::kChunkSize * 2 + 1;
MockTraceWriter* writer = new MockTraceWriter();
TraceBuffer* ring_buffer =
TraceBuffer::CreateTraceBufferRingBuffer(2, writer);
std::string names[HANDLES_COUNT];
for (int i = 0; i < HANDLES_COUNT; ++i) {
names[i] = "Test.EventNo" + std::to_string(i);
}
std::vector<uint64_t> handles(HANDLES_COUNT);
uint8_t category_enabled_flag = 41;
for (size_t i = 0; i < handles.size(); ++i) {
TraceObject* trace_object = ring_buffer->AddTraceEvent(&handles[i]);
CHECK_NOT_NULL(trace_object);
trace_object->Initialize('X', &category_enabled_flag, names[i].c_str(),
"Test.Scope", 42, 123, 0, NULL, NULL, NULL, 0);
trace_object = ring_buffer->GetEventByHandle(handles[i]);
CHECK_NOT_NULL(trace_object);
CHECK_EQ('X', trace_object->phase());
CHECK_EQ(names[i], std::string(trace_object->name()));
CHECK_EQ(category_enabled_flag, *trace_object->category_enabled_flag());
}
// We should only be able to retrieve the last kChunkSize + 1.
for (size_t i = 0; i < TraceBufferChunk::kChunkSize; ++i) {
CHECK_NULL(ring_buffer->GetEventByHandle(handles[i]));
}
for (size_t i = TraceBufferChunk::kChunkSize; i < handles.size(); ++i) {
TraceObject* trace_object = ring_buffer->GetEventByHandle(handles[i]);
CHECK_NOT_NULL(trace_object);
// The object properties should be correct.
CHECK_EQ('X', trace_object->phase());
CHECK_EQ(names[i], std::string(trace_object->name()));
CHECK_EQ(category_enabled_flag, *trace_object->category_enabled_flag());
}
// Check Flush(), that the writer wrote the last kChunkSize 1 event names.
ring_buffer->Flush();
auto events = writer->events();
CHECK_EQ(TraceBufferChunk::kChunkSize + 1, events.size());
for (size_t i = TraceBufferChunk::kChunkSize; i < handles.size(); ++i) {
CHECK_EQ(names[i], events[i - TraceBufferChunk::kChunkSize]);
}
delete ring_buffer;
}
TEST(TestJSONTraceWriter) {
std::ostringstream stream;
v8::Platform* old_platform = i::V8::GetCurrentPlatform();
v8::Platform* default_platform = v8::platform::CreateDefaultPlatform();
i::V8::SetPlatformForTesting(default_platform);
// Create a scope for the tracing controller to terminate the trace writer.
{
TracingController tracing_controller;
platform::SetTracingController(default_platform, &tracing_controller);
TraceWriter* writer = TraceWriter::CreateJSONTraceWriter(stream);
TraceBuffer* ring_buffer =
TraceBuffer::CreateTraceBufferRingBuffer(1, writer);
tracing_controller.Initialize(ring_buffer);
TraceConfig* trace_config = new TraceConfig();
trace_config->AddIncludedCategory("v8-cat");
tracing_controller.StartTracing(trace_config);
TraceObject trace_object;
trace_object.InitializeForTesting(
'X', tracing_controller.GetCategoryGroupEnabled("v8-cat"), "Test0",
v8::internal::tracing::kGlobalScope, 42, 123, 0, NULL, NULL, NULL, 0,
11, 22, 100, 50, 33, 44);
writer->AppendTraceEvent(&trace_object);
trace_object.InitializeForTesting(
'Y', tracing_controller.GetCategoryGroupEnabled("v8-cat"), "Test1",
v8::internal::tracing::kGlobalScope, 43, 456, 0, NULL, NULL, NULL, 0,
55, 66, 110, 55, 77, 88);
writer->AppendTraceEvent(&trace_object);
tracing_controller.StopTracing();
}
std::string trace_str = stream.str();
std::string expected_trace_str =
"{\"traceEvents\":[{\"pid\":11,\"tid\":22,\"ts\":100,\"tts\":50,"
"\"ph\":\"X\",\"cat\":\"v8-cat\",\"name\":\"Test0\",\"args\":{},"
"\"dur\":33,\"tdur\":44},{\"pid\":55,\"tid\":66,\"ts\":110,\"tts\":55,"
"\"ph\":\"Y\",\"cat\":\"v8-cat\",\"name\":\"Test1\",\"args\":{},\"dur\":"
"77,\"tdur\":88}]}";
CHECK_EQ(expected_trace_str, trace_str);
i::V8::SetPlatformForTesting(old_platform);
}
TEST(TestTracingController) {
v8::Platform* old_platform = i::V8::GetCurrentPlatform();
v8::Platform* default_platform = v8::platform::CreateDefaultPlatform();
i::V8::SetPlatformForTesting(default_platform);
TracingController tracing_controller;
platform::SetTracingController(default_platform, &tracing_controller);
MockTraceWriter* writer = new MockTraceWriter();
TraceBuffer* ring_buffer =
TraceBuffer::CreateTraceBufferRingBuffer(1, writer);
tracing_controller.Initialize(ring_buffer);
TraceConfig* trace_config = new TraceConfig();
trace_config->AddIncludedCategory("v8");
tracing_controller.StartTracing(trace_config);
TRACE_EVENT0("v8", "v8.Test");
// cat category is not included in default config
TRACE_EVENT0("cat", "v8.Test2");
TRACE_EVENT0("v8", "v8.Test3");
tracing_controller.StopTracing();
CHECK_EQ(2, writer->events().size());
CHECK_EQ(std::string("v8.Test"), writer->events()[0]);
CHECK_EQ(std::string("v8.Test3"), writer->events()[1]);
i::V8::SetPlatformForTesting(old_platform);
}
} // namespace tracing
} // namespace platform
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment