Skip to content

Commit 76f2c8f

Browse files
authored
Add PerfTrace API and add tracing to XmlHttpRequest (#125)
This PR depends on microsoft/arcana.cpp#51 - Add a public PerfTrace API that aims to provide a platform agnostic way of capturing perf intervals (just wraps arcana's trace_region). The API can be used purely on the native side, but it also includes helper functions for transferring ownership to JS, or taking ownership from JS. This makes it easy to get perf intervals for operations that start in native and end in JS, or vice versa. - Also add a little more tracing to XmlHttpRequest.
1 parent 70ba1e5 commit 76f2c8f

File tree

6 files changed

+165
-19
lines changed

6 files changed

+165
-19
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ include(FetchContent)
1111
# --------------------------------------------------
1212
FetchContent_Declare(arcana.cpp
1313
GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git
14-
GIT_TAG c726dbe58713eda65bfb139c257093c43479b894)
14+
GIT_TAG c02527c32d51b6b3ffeda36f8cf140d2e1c60bb9)
1515
FetchContent_Declare(AndroidExtensions
1616
GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git
17-
GIT_TAG f7ed149b5360cc8a4908fece66607c5ce1e6095b)
17+
GIT_TAG f7ed149b5360cc8a4908fece66607c5ce1e6095b)
1818
FetchContent_Declare(asio
1919
GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git
2020
GIT_TAG f693a3eb7fe72a5f19b975289afc4f437d373d9c)

Core/Foundation/CMakeLists.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
set(SOURCES
22
"Include/Babylon/Api.h"
33
"Include/Babylon/DebugTrace.h"
4-
"Source/DebugTrace.cpp")
4+
"Include/Babylon/PerfTrace.h"
5+
"Source/DebugTrace.cpp"
6+
"Source/PerfTrace.cpp")
57

68
add_library(Foundation ${SOURCES})
79

8-
target_include_directories(Foundation INTERFACE "Include")
10+
target_include_directories(Foundation
11+
PRIVATE "Include/Babylon"
12+
INTERFACE "Include")
13+
14+
target_link_libraries(Foundation
15+
PUBLIC napi
16+
PRIVATE napi-extensions
17+
PRIVATE arcana)
918

1019
set_property(TARGET Foundation PROPERTY FOLDER Core)
1120
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES})
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
#include <memory>
3+
#include <napi/env.h>
4+
5+
6+
namespace Babylon
7+
{
8+
namespace PerfTrace
9+
{
10+
enum class Level
11+
{
12+
// Tracing is a no-op.
13+
None,
14+
// Traces time intervals with platform specific profiling APIs.
15+
Mark,
16+
// Traces time intervals and also performas platform specific logging.
17+
Log,
18+
};
19+
20+
// Controls a perf trace interval. When the handle is destructed, the perf interval ends.
21+
class Handle
22+
{
23+
public:
24+
~Handle();
25+
Handle(const Handle&) = delete;
26+
Handle& operator=(const Handle&) = delete;
27+
Handle(Handle&&) noexcept;
28+
Handle& operator=(Handle&&) noexcept;
29+
30+
// Transfers ownership of the handle to a napi value. After this call, destructing the passed in handle has no effect.
31+
static Napi::Value ToNapi(Napi::Env env, Handle handle);
32+
33+
// Transfers ownership of the handle from a napi value. After this call, destructing the returned handle will end the perf trace interval.
34+
static Handle FromNapi(Napi::Value napiValue);
35+
36+
private:
37+
friend Handle Trace(const char* name);
38+
Handle(const char* name);
39+
class Impl;
40+
std::unique_ptr<Impl> m_impl;
41+
};
42+
43+
// Sets the trace level. It is None by default.
44+
void SetLevel(Level level);
45+
46+
// Starts a perf trace interval. Destructing the returned handle ends the perf trace interval.
47+
Handle Trace(const char* name);
48+
}
49+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#include "PerfTrace.h"
2+
#include <arcana/tracing/trace_region.h>
3+
#include <cstdint>
4+
#include <unordered_map>
5+
6+
namespace
7+
{
8+
std::unordered_map<std::uint32_t, Babylon::PerfTrace::Handle> g_traceHandles;
9+
std::uint32_t g_nextTraceId = 0;
10+
}
11+
12+
namespace Babylon
13+
{
14+
namespace PerfTrace
15+
{
16+
void SetLevel(Level level)
17+
{
18+
switch(level)
19+
{
20+
case Level::None:
21+
arcana::trace_region::disable();
22+
break;
23+
case Level::Mark:
24+
arcana::trace_region::enable(arcana::trace_level::mark);
25+
break;
26+
case Level::Log:
27+
arcana::trace_region::enable(arcana::trace_level::log);
28+
break;
29+
}
30+
}
31+
32+
// Private implementation to hide arcana::trace_region
33+
class Handle::Impl
34+
{
35+
public:
36+
Impl(const char* name) : m_region(name) {}
37+
private:
38+
arcana::trace_region m_region;
39+
};
40+
41+
Handle::Handle(const char* name)
42+
: m_impl(std::make_unique<Impl>(name))
43+
{
44+
}
45+
46+
Handle::~Handle() = default;
47+
Handle::Handle(Handle&&) noexcept = default;
48+
Handle& Handle::operator=(Handle&&) noexcept = default;
49+
50+
Handle Trace(const char* name)
51+
{
52+
return Handle(name);
53+
}
54+
55+
Napi::Value Handle::ToNapi(Napi::Env env, Handle traceHandle)
56+
{
57+
g_nextTraceId++;
58+
g_traceHandles.emplace(g_nextTraceId, std::move(traceHandle));
59+
return Napi::Value::From(env, g_nextTraceId);
60+
}
61+
62+
Handle Handle::FromNapi(Napi::Value napiValue)
63+
{
64+
const std::uint32_t traceId = napiValue.As<Napi::Number>().Uint32Value();
65+
auto it = g_traceHandles.find(traceId);
66+
if (it == g_traceHandles.end())
67+
{
68+
throw std::runtime_error("Invalid TraceHandle ID");
69+
}
70+
71+
Handle traceHandle = std::move(it->second);
72+
g_traceHandles.erase(it);
73+
return traceHandle;
74+
}
75+
}
76+
}

Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "XMLHttpRequest.h"
22
#include <Babylon/JsRuntime.h>
33
#include <Babylon/Polyfills/XMLHttpRequest.h>
4+
#include <arcana/tracing/trace_region.h>
5+
#include <sstream>
46

57
namespace Babylon::Polyfills::Internal
68
{
@@ -216,11 +218,11 @@ namespace Babylon::Polyfills::Internal
216218

217219
void XMLHttpRequest::Open(const Napi::CallbackInfo& info)
218220
{
219-
const auto inputURL = info[1].As<Napi::String>();
221+
m_url = info[1].As<Napi::String>();
220222

221223
try
222224
{
223-
m_request.Open(MethodType::StringToEnum(info[0].As<Napi::String>().Utf8Value()), inputURL);
225+
m_request.Open(MethodType::StringToEnum(info[0].As<Napi::String>().Utf8Value()), m_url);
224226
}
225227
catch (const std::exception& e)
226228
{
@@ -254,19 +256,26 @@ namespace Babylon::Polyfills::Internal
254256
}
255257
}
256258

257-
m_request.SendAsync().then(m_runtimeScheduler, arcana::cancellation::none(), [this]() {
258-
SetReadyState(ReadyState::Done);
259-
RaiseEvent(EventType::LoadEnd);
260-
261-
// Assume the XMLHttpRequest will only be used for a single request and clear the event handlers.
262-
// Single use seems to be the standard pattern, and we need to release our strong refs to event handlers.
263-
m_eventHandlerRefs.clear();
264-
}).then(arcana::inline_scheduler, arcana::cancellation::none(), [env = info.Env()](arcana::expected<void, std::exception_ptr> result) {
265-
if (result.has_error())
266-
{
267-
Napi::Error::New(env, result.error()).ThrowAsJavaScriptException();
268-
}
269-
});
259+
std::string traceName = (std::ostringstream{} << "XMLHttpRequest::Send [" << m_url << "]").str();
260+
auto sendRegion = std::make_optional<arcana::trace_region>(traceName.c_str());
261+
m_request.SendAsync()
262+
.then(arcana::inline_scheduler, arcana::cancellation::none(), [sendRegion{std::move(sendRegion)}]() mutable {
263+
sendRegion.reset();
264+
})
265+
.then(m_runtimeScheduler, arcana::cancellation::none(), [this]() {
266+
SetReadyState(ReadyState::Done);
267+
RaiseEvent(EventType::LoadEnd);
268+
269+
// Assume the XMLHttpRequest will only be used for a single request and clear the event handlers.
270+
// Single use seems to be the standard pattern, and we need to release our strong refs to event handlers.
271+
m_eventHandlerRefs.clear();
272+
})
273+
.then(arcana::inline_scheduler, arcana::cancellation::none(), [env = info.Env()](arcana::expected<void, std::exception_ptr> result) {
274+
if (result.has_error())
275+
{
276+
Napi::Error::New(env, result.error()).ThrowAsJavaScriptException();
277+
}
278+
});
270279
}
271280

272281
void XMLHttpRequest::SetReadyState(ReadyState readyState)
@@ -277,6 +286,8 @@ namespace Babylon::Polyfills::Internal
277286

278287
void XMLHttpRequest::RaiseEvent(const char* eventType)
279288
{
289+
std::string traceName = (std::ostringstream{} << "XMLHttpRequest::RaiseEvent [" << eventType << "] [" << m_url << "]").str();
290+
arcana::trace_region raiseEventRegion{traceName.c_str()};
280291
const auto it = m_eventHandlerRefs.find(eventType);
281292
if (it != m_eventHandlerRefs.end())
282293
{

Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ namespace Babylon::Polyfills::Internal
4444
void SetReadyState(ReadyState readyState);
4545
void RaiseEvent(const char* eventType);
4646

47+
std::string m_url{};
4748
UrlLib::UrlRequest m_request{};
4849
JsRuntimeScheduler m_runtimeScheduler;
4950
ReadyState m_readyState{ReadyState::Unsent};

0 commit comments

Comments
 (0)