Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

added cpu profiler

  • Loading branch information...
commit 8345ba7b85aff94818695fbc9a8a776d1bed6950 1 parent 400fb8a
@dannycoates dannycoates authored
View
9 graph_node.cc
@@ -14,6 +14,7 @@ void GraphNode::Initialize() {
node_template_->SetAccessor(String::New("type"), GraphNode::GetType);
node_template_->SetAccessor(String::New("name"), GraphNode::GetName);
node_template_->SetAccessor(String::New("id"), GraphNode::GetId);
+ node_template_->SetAccessor(String::New("ptr"), GraphNode::GetPtr);
node_template_->SetAccessor(String::New("instancesCount"), GraphNode::GetInstancesCount);
node_template_->SetAccessor(String::New("childrenCount"), GraphNode::GetChildrenCount);
node_template_->SetAccessor(String::New("retainersCount"), GraphNode::GetRetainersCount);
@@ -77,6 +78,14 @@ Handle<Value> GraphNode::GetId(Local<String> property, const AccessorInfo& info)
return scope.Close(Integer::NewFromUnsigned(id));
}
+Handle<Value> GraphNode::GetPtr(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ uint64_t id = reinterpret_cast<uint64_t>(ptr);
+ return scope.Close(Integer::NewFromUnsigned(id));
+}
+
Handle<Value> GraphNode::GetInstancesCount(Local<String> property, const AccessorInfo& info) {
HandleScope scope;
Local<Object> self = info.Holder();
View
1  graph_node.h
@@ -18,6 +18,7 @@ class GraphNode {
static Handle<Value> GetType(Local<String> property, const AccessorInfo& info);
static Handle<Value> GetName(Local<String> property, const AccessorInfo& info);
static Handle<Value> GetId(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetPtr(Local<String> property, const AccessorInfo& info);
static Handle<Value> GetInstancesCount(Local<String> property, const AccessorInfo& info);
static Handle<Value> GetChildrenCount(Local<String> property, const AccessorInfo& info);
static Handle<Value> GetRetainersCount(Local<String> property, const AccessorInfo& info);
View
54 heap.cc
@@ -1,54 +0,0 @@
-#include <v8.h>
-#include <v8-profiler.h>
-#include <node.h>
-#include "snapshot.h"
-
-using namespace v8;
-using namespace node;
-using namespace nodex;
-
-static Handle<Value> TakeSnapshot(const Arguments& args) {
- HandleScope scope;
- Local<String> title = String::New("");
- if (args.Length() > 0) {
- title = args[0]->ToString();
- }
- const HeapSnapshot* snapshot = HeapProfiler::TakeSnapshot(title);
- return scope.Close(Snapshot::New(snapshot));
-}
-
-static Handle<Value> GetSnapshot(const Arguments& args) {
- HandleScope scope;
- if (args.Length() < 1) {
- return ThrowException(Exception::Error(String::New("No index specified")));
- } else if (!args[0]->IsInt32()) {
- return ThrowException(Exception::Error(String::New("Argument must be integer")));
- }
- int32_t index = args[0]->Int32Value();
- const HeapSnapshot* snapshot = HeapProfiler::GetSnapshot(index);
- return scope.Close(Snapshot::New(snapshot));
-}
-
-static Handle<Value> FindSnapshot(const Arguments& args) {
- HandleScope scope;
- if (args.Length() < 1) {
- return ThrowException(Exception::Error(String::New("No uid specified")));
- }
- uint32_t uid = args[0]->Uint32Value();
- const HeapSnapshot* snapshot = HeapProfiler::FindSnapshot(uid);
- return scope.Close(Snapshot::New(snapshot));
-}
-
-static Handle<Value> GetSnapshotsCount(const Arguments& args) {
- HandleScope scope;
- return scope.Close(Integer::New(HeapProfiler::GetSnapshotsCount()));
-}
-
-extern "C" void init(Handle<Object> target) {
- HandleScope scope;
-
- NODE_SET_METHOD(target, "takeSnapshot", TakeSnapshot);
- NODE_SET_METHOD(target, "getSnapshot", GetSnapshot);
- NODE_SET_METHOD(target, "findSnapshot", FindSnapshot);
- NODE_SET_METHOD(target, "snapshotCount", GetSnapshotsCount);
-}
View
69 profile.cc
@@ -0,0 +1,69 @@
+#include "profile.h"
+#include "profile_node.h"
+
+using namespace v8;
+
+namespace nodex {
+
+Persistent<ObjectTemplate> Profile::profile_template_;
+
+void Profile::Initialize() {
+ profile_template_ = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ profile_template_->SetInternalFieldCount(1);
+ profile_template_->SetAccessor(String::New("title"), Profile::GetTitle);
+ profile_template_->SetAccessor(String::New("uid"), Profile::GetUid);
+ profile_template_->SetAccessor(String::New("topRoot"), Profile::GetTopRoot);
+ profile_template_->SetAccessor(String::New("bottomRoot"), Profile::GetBottomRoot);
+}
+
+Handle<Value> Profile::GetUid(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ uint32_t uid = static_cast<CpuProfile*>(ptr)->GetUid();
+ return scope.Close(Integer::NewFromUnsigned(uid));
+}
+
+
+Handle<Value> Profile::GetTitle(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ Handle<String> title = static_cast<CpuProfile*>(ptr)->GetTitle();
+ return scope.Close(title);
+}
+
+Handle<Value> Profile::GetTopRoot(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ const CpuProfileNode* node = static_cast<CpuProfile*>(ptr)->GetTopDownRoot();
+ return scope.Close(ProfileNode::New(node));
+}
+
+
+Handle<Value> Profile::GetBottomRoot(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ const CpuProfileNode* node = static_cast<CpuProfile*>(ptr)->GetBottomUpRoot();
+ return scope.Close(ProfileNode::New(node));
+}
+
+Handle<Value> Profile::New(const CpuProfile* profile) {
+ HandleScope scope;
+
+ if (profile_template_.IsEmpty()) {
+ Profile::Initialize();
+ }
+
+ if(!profile) {
+ return Undefined();
+ }
+ else {
+ Local<Object> obj = profile_template_->NewInstance();
+ obj->SetPointerInInternalField(0, const_cast<CpuProfile*>(profile));
+ return scope.Close(obj);
+ }
+}
+}
View
27 profile.h
@@ -0,0 +1,27 @@
+
+
+#ifndef NODE_PROFILE_
+#define NODE_PROFILE_
+
+#include <v8.h>
+#include <v8-profiler.h>
+
+using namespace v8;
+
+namespace nodex {
+
+class Profile {
+ public:
+ static Handle<Value> New(const CpuProfile* profile);
+
+ private:
+ static Handle<Value> GetUid(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetTitle(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetTopRoot(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetBottomRoot(Local<String> property, const AccessorInfo& info);
+ static void Initialize();
+ static Persistent<ObjectTemplate> profile_template_;
+};
+
+}
+#endif // NODE_PROFILE_
View
126 profile_node.cc
@@ -0,0 +1,126 @@
+#include "profile_node.h"
+
+using namespace v8;
+
+namespace nodex {
+
+Persistent<ObjectTemplate> ProfileNode::node_template_;
+
+void ProfileNode::Initialize() {
+ node_template_ = Persistent<ObjectTemplate>::New(ObjectTemplate::New());
+ node_template_->SetInternalFieldCount(1);
+ node_template_->SetAccessor(String::New("functionName"), ProfileNode::GetFunctionName);
+ node_template_->SetAccessor(String::New("scriptName"), ProfileNode::GetScriptName);
+ node_template_->SetAccessor(String::New("lineNumber"), ProfileNode::GetLineNumber);
+ node_template_->SetAccessor(String::New("totalTime"), ProfileNode::GetTotalTime);
+ node_template_->SetAccessor(String::New("selfTime"), ProfileNode::GetSelfTime);
+ node_template_->SetAccessor(String::New("totalSamplesCount"), ProfileNode::GetTotalSamplesCount);
+ node_template_->SetAccessor(String::New("selfSamplesCount"), ProfileNode::GetSelfSamplesCount);
+ node_template_->SetAccessor(String::New("callUid"), ProfileNode::GetCallUid);
+ node_template_->SetAccessor(String::New("childrenCount"), ProfileNode::GetChildrenCount);
+ node_template_->Set(String::New("getChild"), FunctionTemplate::New(ProfileNode::GetChild));
+}
+
+Handle<Value> ProfileNode::GetFunctionName(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ Handle<String> fname = static_cast<CpuProfileNode*>(ptr)->GetFunctionName();
+ return scope.Close(fname);
+}
+
+Handle<Value> ProfileNode::GetScriptName(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ Handle<String> sname = static_cast<CpuProfileNode*>(ptr)->GetScriptResourceName();
+ return scope.Close(sname);
+}
+
+Handle<Value> ProfileNode::GetLineNumber(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ int32_t ln = static_cast<CpuProfileNode*>(ptr)->GetLineNumber();
+ return scope.Close(Integer::New(ln));
+}
+
+Handle<Value> ProfileNode::GetTotalTime(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ double ttime = static_cast<CpuProfileNode*>(ptr)->GetTotalTime();
+ return scope.Close(Number::New(ttime));
+}
+
+Handle<Value> ProfileNode::GetSelfTime(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ double stime = static_cast<CpuProfileNode*>(ptr)->GetSelfTime();
+ return scope.Close(Number::New(stime));
+}
+
+Handle<Value> ProfileNode::GetTotalSamplesCount(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ double samples = static_cast<CpuProfileNode*>(ptr)->GetTotalSamplesCount();
+ return scope.Close(Number::New(samples));
+}
+
+Handle<Value> ProfileNode::GetSelfSamplesCount(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ double samples = static_cast<CpuProfileNode*>(ptr)->GetSelfSamplesCount();
+ return scope.Close(Number::New(samples));
+}
+
+Handle<Value> ProfileNode::GetCallUid(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ uint32_t uid = static_cast<CpuProfileNode*>(ptr)->GetCallUid();
+ return scope.Close(Integer::NewFromUnsigned(uid));
+}
+
+Handle<Value> ProfileNode::GetChildrenCount(Local<String> property, const AccessorInfo& info) {
+ HandleScope scope;
+ Local<Object> self = info.Holder();
+ void* ptr = self->GetPointerFromInternalField(0);
+ int32_t count = static_cast<CpuProfileNode*>(ptr)->GetChildrenCount();
+ return scope.Close(Integer::New(count));
+}
+
+Handle<Value> ProfileNode::GetChild(const Arguments& args) {
+ HandleScope scope;
+ if (args.Length() < 1) {
+ return ThrowException(Exception::Error(String::New("No index specified")));
+ } else if (!args[0]->IsInt32()) {
+ return ThrowException(Exception::Error(String::New("Argument must be integer")));
+ }
+ int32_t index = args[0]->Int32Value();
+ Handle<Object> self = args.This();
+ void* ptr = self->GetPointerFromInternalField(0);
+ const CpuProfileNode* node = static_cast<CpuProfileNode*>(ptr)->GetChild(index);
+ return scope.Close(ProfileNode::New(node));
+}
+
+Handle<Value> ProfileNode::New(const CpuProfileNode* node) {
+ HandleScope scope;
+
+ if (node_template_.IsEmpty()) {
+ ProfileNode::Initialize();
+ }
+
+ if(!node) {
+ return Undefined();
+ }
+ else {
+ Local<Object> obj = node_template_->NewInstance();
+ obj->SetPointerInInternalField(0, const_cast<CpuProfileNode*>(node));
+ return scope.Close(obj);
+ }
+}
+}
View
33 profile_node.h
@@ -0,0 +1,33 @@
+
+
+#ifndef NODE_PROFILE_NODE_
+#define NODE_PROFILE_NODE_
+
+#include <v8.h>
+#include <v8-profiler.h>
+
+using namespace v8;
+
+namespace nodex {
+
+class ProfileNode {
+ public:
+ static Handle<Value> New(const CpuProfileNode* node);
+
+ private:
+ static Handle<Value> GetFunctionName(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetScriptName(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetLineNumber(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetTotalTime(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetSelfTime(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetTotalSamplesCount(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetSelfSamplesCount(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetCallUid(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetChildrenCount(Local<String> property, const AccessorInfo& info);
+ static Handle<Value> GetChild(const Arguments& args);
+ static void Initialize();
+ static Persistent<ObjectTemplate> node_template_;
+};
+
+}
+#endif // NODE_PROFILE_NODE_
View
109 profiler.cc
@@ -0,0 +1,109 @@
+#include <v8.h>
+#include <v8-profiler.h>
+#include <node.h>
+#include "snapshot.h"
+#include "profile.h"
+
+using namespace v8;
+using namespace node;
+using namespace nodex;
+
+static Handle<Value> TakeSnapshot(const Arguments& args) {
+ HandleScope scope;
+ Local<String> title = String::New("");
+ int32_t len = args.Length();
+ HeapSnapshot::Type mode = HeapSnapshot::kAggregated;
+ if (len > 0) {
+ title = args[0]->ToString();
+ }
+ if (len > 1 && args[1]->IsInt32()) {
+ mode = static_cast<HeapSnapshot::Type>(args[1]->Int32Value());
+ }
+ const HeapSnapshot* snapshot = HeapProfiler::TakeSnapshot(title, mode);
+ return scope.Close(Snapshot::New(snapshot));
+}
+
+static Handle<Value> GetSnapshot(const Arguments& args) {
+ HandleScope scope;
+ if (args.Length() < 1) {
+ return ThrowException(Exception::Error(String::New("No index specified")));
+ } else if (!args[0]->IsInt32()) {
+ return ThrowException(Exception::TypeError(String::New("Argument must be an integer")));
+ }
+ int32_t index = args[0]->Int32Value();
+ const HeapSnapshot* snapshot = HeapProfiler::GetSnapshot(index);
+ return scope.Close(Snapshot::New(snapshot));
+}
+
+static Handle<Value> FindSnapshot(const Arguments& args) {
+ HandleScope scope;
+ if (args.Length() < 1) {
+ return ThrowException(Exception::Error(String::New("No uid specified")));
+ }
+ uint32_t uid = args[0]->Uint32Value();
+ const HeapSnapshot* snapshot = HeapProfiler::FindSnapshot(uid);
+ return scope.Close(Snapshot::New(snapshot));
+}
+
+static Handle<Value> GetSnapshotsCount(const Arguments& args) {
+ HandleScope scope;
+ return scope.Close(Integer::New(HeapProfiler::GetSnapshotsCount()));
+}
+
+Handle<Value> StartProfiling(const Arguments& args) {
+ HandleScope scope;
+ Local<String> title = args.Length() > 0 ? args[0]->ToString() : String::New("");
+ v8::CpuProfiler::StartProfiling(title);
+ return Undefined();
+}
+
+Handle<Value> StopProfiling(const Arguments& args) {
+ HandleScope scope;
+ Local<String> title = args.Length() > 0 ? args[0]->ToString() : String::New("");
+ const CpuProfile* profile = v8::CpuProfiler::StopProfiling(title);
+ return scope.Close(Profile::New(profile));
+}
+
+Handle<Value> GetProfile(const Arguments& args) {
+ HandleScope scope;
+ if (args.Length() < 1) {
+ return ThrowException(Exception::Error(String::New("No index specified")));
+ } else if (!args[0]->IsInt32()) {
+ return ThrowException(Exception::TypeError(String::New("Argument must be an integer")));
+ }
+ int32_t index = args[0]->Int32Value();
+ const CpuProfile* profile = v8::CpuProfiler::GetProfile(index);
+ return scope.Close(Profile::New(profile));
+}
+
+Handle<Value> FindProfile(const Arguments& args) {
+ HandleScope scope;
+ if (args.Length() < 1) {
+ return ThrowException(Exception::Error(String::New("No index specified")));
+ } else if (!args[0]->IsInt32()) {
+ return ThrowException(Exception::TypeError(String::New("Argument must be an integer")));
+ }
+ uint32_t uid = args[0]->Uint32Value();
+ const CpuProfile* profile = v8::CpuProfiler::FindProfile(uid);
+ return scope.Close(Profile::New(profile));
+}
+
+Handle<Value> GetProfilesCount(const Arguments& args) {
+ HandleScope scope;
+ return scope.Close(Integer::New(v8::CpuProfiler::GetProfilesCount()));
+}
+
+extern "C" void init(Handle<Object> target) {
+ HandleScope scope;
+
+ NODE_SET_METHOD(target, "takeSnapshot", TakeSnapshot);
+ NODE_SET_METHOD(target, "getSnapshot", GetSnapshot);
+ NODE_SET_METHOD(target, "findSnapshot", FindSnapshot);
+ NODE_SET_METHOD(target, "snapshotCount", GetSnapshotsCount);
+
+ NODE_SET_METHOD(target, "startProfiling", StartProfiling);
+ NODE_SET_METHOD(target, "stopProfiling", StopProfiling);
+ NODE_SET_METHOD(target, "getProfile", GetProfile);
+ NODE_SET_METHOD(target, "findProfile", FindProfile);
+ NODE_SET_METHOD(target, "getProfilesCount", GetProfilesCount);
+}
View
22 test.js
@@ -1,22 +0,0 @@
-var assert = require('assert'),
- heap = require('./build/default/heap-profiler');
-
-var x = heap.takeSnapshot('hello world');
-
-assert.equal(heap.snapshotCount(), 1);
-assert.equal(x.title, 'hello world');
-var root = x.root;
-assert.notEqual(root, null);
-
-assert.ok(x.root.childrenCount > 0);
-
-for (var i = 0; i < root.childrenCount; i++) {
- assert.notEqual(root.getChild(i), undefined);
-}
-
-var y = heap.takeSnapshot();
-
-var delta = x.compareWith(y);
-assert.notEqual(delta.additions, null);
-
-console.log('done');
View
120 v8-profiler.js
@@ -0,0 +1,120 @@
+var binding = require("./build/default/profiler");
+
+function Snapshot() {}
+
+//adapted from WebCore/bindings/v8/ScriptHeapSnapshot.cpp
+Snapshot.prototype.stringify = function() {
+ var root = this.root, i, j, count_i, count_j, node,
+ lowLevels = {}, entries = {}, entry,
+ children = {}, child, edge, result = {};
+ for (i = 0, count_i = root.childrenCount; i < count_i; i++) {
+ node = root.getChild(i).to;
+ if (node.type === 'Hidden') {
+ lowLevels[node.name] = {
+ count: node.instancesCount,
+ size: node.size,
+ type: node.name
+ };
+ }
+ else if (node.instancesCount > 0) {
+ entries[node.name] = {
+ constructorName: node.name,
+ count: node.instancesCount,
+ size: node.size
+ };
+ }
+ else {
+ entry = {
+ constructorName: node.name
+ };
+ for(j = 0, count_j = node.childrenCount; j < count_j; j++) {
+ edge = node.getChild(j);
+ child = edge.to;
+ entry[child.ptr.toString()] = {
+ constructorName: child.name,
+ count: parseInt(edge.name, 10)
+ }
+ }
+ children[node.ptr.toString()] = entry;
+ }
+ }
+ result.lowlevels = lowLevels;
+ result.entries = entries;
+ result.children = children;
+ return JSON.stringify(result);
+}
+
+function CpuProfile() {}
+
+function inspectorObjectFor(node) {
+ var i, count, child,
+ result = {
+ functionName: node.functionName,
+ url: node.scriptName,
+ lineNumber: node.lineNumber,
+ totalTime: node.totalTime,
+ selfTime: node.selfTime,
+ numberOfCalls: 0,
+ visible: true,
+ callUID: node.callUid,
+ children: []
+ };
+ for(i = 0, count = node.childrenCount; i < count; i++) {
+ child = node.getChild(i);
+ result.children.push(inspectorObjectFor(child));
+ }
+ return result;
+}
+
+CpuProfile.prototype.stringify = function() {
+ return JSON.stringify(inspectorObjectFor(this.topRoot));
+}
+
+var heapCache = [];
+
+exports.takeSnapshot = function(name, mode) {
+ var type = (mode === 'full') ? 0 : 1;
+ var snapshot = binding.takeSnapshot(name, type);
+ snapshot.__proto__ = Snapshot.prototype;
+ heapCache.push(snapshot);
+ return snapshot;
+}
+
+exports.getSnapshot = function(index) {
+ return heapCache[index];
+}
+
+exports.findSnapshot = function(uid) {
+ return heapCache.filter(function(s) {return s.uid === uid;})[0];
+}
+
+exports.snapshotCount = function() {
+ return heapCache.length;
+}
+
+var cpuCache = [];
+
+exports.startProfiling = function(name) {
+ binding.startProfiling(name);
+}
+
+exports.stopProfiling = function(name) {
+ var profile = binding.stopProfiling(name);
+ profile.__proto__ = CpuProfile.prototype;
+ cpuCache.push(profile);
+ return profile;
+}
+
+exports.getProfile = function(index) {
+ return cpuCache[index];
+}
+
+exports.findProfile = function(uid) {
+ return cpuCache.filter(function(s) {return s.uid === uid;})[0];
+}
+
+exports.profileCount = function() {
+ return cpuCache.length;
+}
+
+process.profiler = exports;
View
6 wscript
@@ -11,12 +11,14 @@ def configure(ctx):
def build(ctx):
t = ctx.new_task_gen('cxx', 'shlib', 'node_addon')
- t.target = 'heap-profiler'
+ t.target = 'profiler'
t.source = """
snapshot.cc
graph_edge.cc
graph_node.cc
graph_path.cc
snapshot_diff.cc
- heap.cc
+ profile.cc
+ profile_node.cc
+ profiler.cc
"""

0 comments on commit 8345ba7

Please sign in to comment.
Something went wrong with that request. Please try again.