react-native-nitro-tracing 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/NitroTracing.podspec +32 -0
- package/README.md +75 -0
- package/android/CMakeLists.txt +49 -0
- package/android/build.gradle +133 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/proguard-rules.pro +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +47 -0
- package/android/src/main/java/com/margelo/nitro/nitrotracing/HermesSamplingGuard.kt +43 -0
- package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingFrameSource.kt +36 -0
- package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingPackage.kt +31 -0
- package/client.ts +15 -0
- package/cpp/HermesSampling.cpp +26 -0
- package/cpp/HermesSampling.hpp +10 -0
- package/cpp/HybridRecording.cpp +66 -0
- package/cpp/HybridRecording.hpp +30 -0
- package/cpp/HybridTraceSpan.cpp +10 -0
- package/cpp/HybridTraceSpan.hpp +17 -0
- package/cpp/HybridTracing.cpp +10 -0
- package/cpp/HybridTracing.hpp +10 -0
- package/cpp/NativeConversions.cpp +77 -0
- package/cpp/NativeConversions.hpp +15 -0
- package/cpp/core/NativeSampler.cpp +152 -0
- package/cpp/core/NativeSampler.hpp +56 -0
- package/cpp/core/Recorder.cpp +218 -0
- package/cpp/core/Recorder.hpp +60 -0
- package/cpp/core/Retention.cpp +26 -0
- package/cpp/core/Retention.hpp +7 -0
- package/cpp/core/TraceJson.cpp +214 -0
- package/cpp/core/TraceJson.hpp +7 -0
- package/cpp/core/TraceTypes.hpp +62 -0
- package/expo.ts +2 -0
- package/ios/NitroTracingFrameSource.mm +35 -0
- package/lib/commonjs/client.cjs +767 -0
- package/lib/commonjs/client.cjs.map +7 -0
- package/lib/commonjs/expo.cjs +88 -0
- package/lib/commonjs/expo.cjs.map +7 -0
- package/lib/commonjs/index.cjs +28 -0
- package/lib/commonjs/index.cjs.map +7 -0
- package/lib/commonjs/performance.cjs +112 -0
- package/lib/commonjs/performance.cjs.map +7 -0
- package/lib/commonjs/plugins.cjs +477 -0
- package/lib/commonjs/plugins.cjs.map +7 -0
- package/lib/commonjs/react.cjs +3833 -0
- package/lib/commonjs/react.cjs.map +7 -0
- package/lib/commonjs/release-profiler.cjs +142 -0
- package/lib/commonjs/release-profiler.cjs.map +7 -0
- package/lib/commonjs/sentry.cjs +188 -0
- package/lib/commonjs/sentry.cjs.map +7 -0
- package/lib/module/client.mjs +768 -0
- package/lib/module/client.mjs.map +7 -0
- package/lib/module/expo.mjs +62 -0
- package/lib/module/expo.mjs.map +7 -0
- package/lib/module/index.mjs +7 -0
- package/lib/module/index.mjs.map +7 -0
- package/lib/module/performance.mjs +89 -0
- package/lib/module/performance.mjs.map +7 -0
- package/lib/module/plugins.mjs +461 -0
- package/lib/module/plugins.mjs.map +7 -0
- package/lib/module/react.mjs +3851 -0
- package/lib/module/react.mjs.map +7 -0
- package/lib/module/release-profiler.mjs +137 -0
- package/lib/module/release-profiler.mjs.map +7 -0
- package/lib/module/sentry.mjs +165 -0
- package/lib/module/sentry.mjs.map +7 -0
- package/lib/typescript/client.d.ts +4 -0
- package/lib/typescript/expo.d.ts +2 -0
- package/lib/typescript/performance.d.ts +2 -0
- package/lib/typescript/plugins.d.ts +12 -0
- package/lib/typescript/react.d.ts +5 -0
- package/lib/typescript/release-profiler.d.ts +2 -0
- package/lib/typescript/sentry.d.ts +2 -0
- package/lib/typescript/src/client/TracingSink.d.ts +10 -0
- package/lib/typescript/src/client/createTraceClient.d.ts +82 -0
- package/lib/typescript/src/client/playground.d.ts +12 -0
- package/lib/typescript/src/expo/ExpoAdapter.d.ts +15 -0
- package/lib/typescript/src/expo/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/plugins/TracePlugin.d.ts +22 -0
- package/lib/typescript/src/plugins/errors.d.ts +15 -0
- package/lib/typescript/src/plugins/nativeMetrics.d.ts +13 -0
- package/lib/typescript/src/plugins/navigation.d.ts +29 -0
- package/lib/typescript/src/plugins/network.d.ts +23 -0
- package/lib/typescript/src/plugins/orderSpans.d.ts +3 -0
- package/lib/typescript/src/plugins/performance.d.ts +13 -0
- package/lib/typescript/src/plugins/releaseProfiler.d.ts +25 -0
- package/lib/typescript/src/plugins/runtimeMetrics.d.ts +17 -0
- package/lib/typescript/src/plugins/sentry.d.ts +15 -0
- package/lib/typescript/src/plugins/startPlugins.d.ts +4 -0
- package/lib/typescript/src/react/DetailView.d.ts +11 -0
- package/lib/typescript/src/react/ExplorerFilters.d.ts +9 -0
- package/lib/typescript/src/react/ExplorerView.d.ts +9 -0
- package/lib/typescript/src/react/InspectorControls.d.ts +229 -0
- package/lib/typescript/src/react/InspectorHeader.d.ts +8 -0
- package/lib/typescript/src/react/InspectorSheet.d.ts +16 -0
- package/lib/typescript/src/react/MetricsView.d.ts +7 -0
- package/lib/typescript/src/react/SummaryView.d.ts +11 -0
- package/lib/typescript/src/react/TimelineView.d.ts +8 -0
- package/lib/typescript/src/react/TraceInspector.d.ts +11 -0
- package/lib/typescript/src/react/TraceOverlay.d.ts +17 -0
- package/lib/typescript/src/react/explorerModel.d.ts +38 -0
- package/lib/typescript/src/react/labels.d.ts +203 -0
- package/lib/typescript/src/react/native.d.ts +42 -0
- package/lib/typescript/src/react/readSnapshot.d.ts +4 -0
- package/lib/typescript/src/react/store.d.ts +9 -0
- package/lib/typescript/src/react/topics.d.ts +80 -0
- package/lib/typescript/src/react/useAppReadyMetric.d.ts +3 -0
- package/lib/typescript/src/react/useLiveRecording.d.ts +4 -0
- package/lib/typescript/src/react/useTraceViewer.d.ts +140 -0
- package/lib/typescript/src/react/viewerModel.d.ts +59 -0
- package/lib/typescript/src/specs/Recording.nitro.d.ts +31 -0
- package/lib/typescript/src/specs/TraceSpan.nitro.d.ts +14 -0
- package/lib/typescript/src/specs/Tracing.nitro.d.ts +17 -0
- package/lib/typescript/src/types/CompletedSpanOptions.d.ts +15 -0
- package/lib/typescript/src/types/EventContext.d.ts +8 -0
- package/lib/typescript/src/types/MarkEvent.d.ts +4 -0
- package/lib/typescript/src/types/MarkOptions.d.ts +6 -0
- package/lib/typescript/src/types/MetricEvent.d.ts +8 -0
- package/lib/typescript/src/types/MetricOptions.d.ts +10 -0
- package/lib/typescript/src/types/NativeSamplingOptions.d.ts +7 -0
- package/lib/typescript/src/types/ReadOptions.d.ts +7 -0
- package/lib/typescript/src/types/RecordingOptions.d.ts +11 -0
- package/lib/typescript/src/types/RecordingStats.d.ts +25 -0
- package/lib/typescript/src/types/SpanEvent.d.ts +13 -0
- package/lib/typescript/src/types/SpanOptions.d.ts +6 -0
- package/lib/typescript/src/types/SpanOutcome.d.ts +2 -0
- package/lib/typescript/src/types/TraceAttribute.d.ts +7 -0
- package/lib/typescript/src/types/TraceAttributes.d.ts +3 -0
- package/lib/typescript/src/types/TraceContext.d.ts +10 -0
- package/lib/typescript/src/types/TracePage.d.ts +18 -0
- package/lib/typescript/src/types.d.ts +17 -0
- package/nitro.json +20 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroTracing+autolinking.cmake +83 -0
- package/nitrogen/generated/android/NitroTracing+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroTracingOnLoad.cpp +49 -0
- package/nitrogen/generated/android/NitroTracingOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotracing/NitroTracingOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroTracing+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NitroTracingAutolinking.mm +35 -0
- package/nitrogen/generated/ios/NitroTracingAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/CompletedSpanOptions.hpp +122 -0
- package/nitrogen/generated/shared/c++/HybridRecordingSpec.cpp +31 -0
- package/nitrogen/generated/shared/c++/HybridRecordingSpec.hpp +100 -0
- package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/HybridTracingSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridTracingSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/MarkEvent.hpp +102 -0
- package/nitrogen/generated/shared/c++/MarkOptions.hpp +99 -0
- package/nitrogen/generated/shared/c++/MetricEvent.hpp +110 -0
- package/nitrogen/generated/shared/c++/MetricOptions.hpp +107 -0
- package/nitrogen/generated/shared/c++/NativeSamplingOptions.hpp +87 -0
- package/nitrogen/generated/shared/c++/ReadOptions.hpp +87 -0
- package/nitrogen/generated/shared/c++/RecordingOptions.hpp +95 -0
- package/nitrogen/generated/shared/c++/RecordingStats.hpp +123 -0
- package/nitrogen/generated/shared/c++/SpanEvent.hpp +121 -0
- package/nitrogen/generated/shared/c++/SpanOptions.hpp +99 -0
- package/nitrogen/generated/shared/c++/SpanOutcome.hpp +84 -0
- package/nitrogen/generated/shared/c++/TraceAttribute.hpp +87 -0
- package/nitrogen/generated/shared/c++/TracePage.hpp +111 -0
- package/package.json +236 -0
- package/performance.ts +2 -0
- package/plugins.ts +24 -0
- package/react-native.config.js +16 -0
- package/react.ts +10 -0
- package/release-profiler.ts +6 -0
- package/sentry.ts +2 -0
- package/src/client/TracingSink.ts +25 -0
- package/src/client/createTraceClient.ts +439 -0
- package/src/client/playground.ts +143 -0
- package/src/expo/ExpoAdapter.ts +71 -0
- package/src/expo/index.ts +2 -0
- package/src/index.ts +8 -0
- package/src/plugins/TracePlugin.ts +22 -0
- package/src/plugins/errors.ts +69 -0
- package/src/plugins/nativeMetrics.ts +26 -0
- package/src/plugins/navigation.ts +111 -0
- package/src/plugins/network.ts +178 -0
- package/src/plugins/orderSpans.ts +22 -0
- package/src/plugins/performance.ts +120 -0
- package/src/plugins/releaseProfiler.ts +123 -0
- package/src/plugins/runtimeMetrics.ts +131 -0
- package/src/plugins/sentry.ts +176 -0
- package/src/plugins/startPlugins.ts +58 -0
- package/src/react/DetailView.tsx +514 -0
- package/src/react/ExplorerFilters.tsx +151 -0
- package/src/react/ExplorerView.tsx +243 -0
- package/src/react/InspectorControls.tsx +651 -0
- package/src/react/InspectorHeader.tsx +171 -0
- package/src/react/InspectorSheet.tsx +89 -0
- package/src/react/MetricsView.tsx +182 -0
- package/src/react/SummaryView.tsx +121 -0
- package/src/react/TimelineView.tsx +87 -0
- package/src/react/TraceInspector.tsx +196 -0
- package/src/react/TraceOverlay.tsx +585 -0
- package/src/react/explorerModel.ts +285 -0
- package/src/react/labels.ts +234 -0
- package/src/react/native.tsx +159 -0
- package/src/react/readSnapshot.ts +58 -0
- package/src/react/store.ts +31 -0
- package/src/react/topics.ts +427 -0
- package/src/react/useAppReadyMetric.ts +10 -0
- package/src/react/useLiveRecording.ts +43 -0
- package/src/react/useTraceViewer.ts +333 -0
- package/src/react/viewerModel.ts +172 -0
- package/src/specs/Recording.nitro.ts +40 -0
- package/src/specs/TraceSpan.nitro.ts +14 -0
- package/src/specs/Tracing.nitro.ts +14 -0
- package/src/types/CompletedSpanOptions.ts +15 -0
- package/src/types/EventContext.ts +8 -0
- package/src/types/MarkEvent.ts +3 -0
- package/src/types/MarkOptions.ts +6 -0
- package/src/types/MetricEvent.ts +8 -0
- package/src/types/MetricOptions.ts +10 -0
- package/src/types/NativeSamplingOptions.ts +7 -0
- package/src/types/ReadOptions.ts +7 -0
- package/src/types/RecordingOptions.ts +11 -0
- package/src/types/RecordingStats.ts +25 -0
- package/src/types/SpanEvent.ts +13 -0
- package/src/types/SpanOptions.ts +6 -0
- package/src/types/SpanOutcome.ts +2 -0
- package/src/types/TraceAttribute.ts +7 -0
- package/src/types/TraceAttributes.ts +4 -0
- package/src/types/TraceContext.ts +10 -0
- package/src/types/TracePage.ts +18 -0
- package/src/types.ts +17 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#include "HybridRecording.hpp"
|
|
2
|
+
#include "HybridTraceSpan.hpp"
|
|
3
|
+
#include "NativeConversions.hpp"
|
|
4
|
+
#include "core/TraceJson.hpp"
|
|
5
|
+
#include <cmath>
|
|
6
|
+
#include <stdexcept>
|
|
7
|
+
namespace margelo::nitro::nitrotracing {
|
|
8
|
+
HybridRecording::HybridRecording(tracingcore::Config c)
|
|
9
|
+
: HybridObject(TAG), recorder_(std::make_shared<tracingcore::Recorder>(c)) {}
|
|
10
|
+
HybridRecording::~HybridRecording() {
|
|
11
|
+
stopNativeSampling();
|
|
12
|
+
recorder_->stop();
|
|
13
|
+
}
|
|
14
|
+
std::shared_ptr<HybridTraceSpanSpec> HybridRecording::startSpan(const SpanOptions &o) {
|
|
15
|
+
auto token = recorder_->startSpan(toCore(o.name, o.correlationId, o.attributes), o.parentSpanId.value_or(""));
|
|
16
|
+
return std::make_shared<HybridTraceSpan>(recorder_, token);
|
|
17
|
+
}
|
|
18
|
+
std::string HybridRecording::recordSpan(const CompletedSpanOptions &o) {
|
|
19
|
+
return recorder_->recordSpan(toCore(o.name, o.correlationId, o.attributes), o.parentSpanId.value_or(""),
|
|
20
|
+
o.timestampMs, o.durationMs, toCore(o.outcome), o.sourceSpanId.value_or(""),
|
|
21
|
+
o.sourceParentSpanId.value_or(""));
|
|
22
|
+
}
|
|
23
|
+
void HybridRecording::mark(const MarkOptions &o) {
|
|
24
|
+
recorder_->mark(toCore(o.name, o.correlationId, o.attributes), o.timestampMs);
|
|
25
|
+
}
|
|
26
|
+
void HybridRecording::recordMetric(const MetricOptions &o) {
|
|
27
|
+
recorder_->metric(toCore(o.name, o.correlationId, o.attributes), o.value, o.unit, o.timestampMs);
|
|
28
|
+
}
|
|
29
|
+
std::shared_ptr<Promise<TracePage>> HybridRecording::readEvents(const ReadOptions &o) {
|
|
30
|
+
auto after = checkedInteger(o.afterSequence, 9007199254740991.0, "afterSequence");
|
|
31
|
+
auto limit = checkedInteger(o.limit, 1000, "limit");
|
|
32
|
+
auto recorder = recorder_;
|
|
33
|
+
return Promise<TracePage>::async([recorder, after, limit]() { return toNative(recorder->read(after, limit)); });
|
|
34
|
+
}
|
|
35
|
+
RecordingStats HybridRecording::getStats() { return toNative(recorder_->stats()); }
|
|
36
|
+
void HybridRecording::stop() {
|
|
37
|
+
stopNativeSampling();
|
|
38
|
+
recorder_->stop();
|
|
39
|
+
}
|
|
40
|
+
std::shared_ptr<Promise<std::string>> HybridRecording::exportJson() {
|
|
41
|
+
auto recorder = recorder_;
|
|
42
|
+
return Promise<std::string>::async([recorder]() { return tracingcore::exportJson(recorder->snapshot()); });
|
|
43
|
+
}
|
|
44
|
+
size_t HybridRecording::getExternalMemorySize() noexcept { return recorder_->memorySize(); }
|
|
45
|
+
void HybridRecording::dispose() {
|
|
46
|
+
stopNativeSampling();
|
|
47
|
+
recorder_->clear();
|
|
48
|
+
}
|
|
49
|
+
std::shared_ptr<Promise<std::string>> HybridRecording::exportTraceEvents() {
|
|
50
|
+
auto recorder = recorder_;
|
|
51
|
+
return Promise<std::string>::async([recorder]() { return tracingcore::exportTraceEvents(recorder->snapshot()); });
|
|
52
|
+
}
|
|
53
|
+
void HybridRecording::startNativeSampling(const NativeSamplingOptions &o) {
|
|
54
|
+
if (!std::isfinite(o.intervalMs) || o.intervalMs < 250 || o.intervalMs > 60000)
|
|
55
|
+
throw std::invalid_argument("Native sampling interval must be 250..60000 ms");
|
|
56
|
+
if (!recorder_->stats().recording)
|
|
57
|
+
throw std::runtime_error("Recording has stopped");
|
|
58
|
+
std::lock_guard lock(samplerMutex_);
|
|
59
|
+
sampler_.reset(); // Join the previous thread before starting its replacement.
|
|
60
|
+
sampler_ = std::make_unique<tracingcore::NativeSampler>(recorder_, o.intervalMs, o.frames);
|
|
61
|
+
}
|
|
62
|
+
void HybridRecording::stopNativeSampling() {
|
|
63
|
+
std::lock_guard lock(samplerMutex_);
|
|
64
|
+
sampler_.reset();
|
|
65
|
+
}
|
|
66
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "HybridRecordingSpec.hpp"
|
|
3
|
+
#include "core/NativeSampler.hpp"
|
|
4
|
+
#include "core/Recorder.hpp"
|
|
5
|
+
#include <mutex>
|
|
6
|
+
namespace margelo::nitro::nitrotracing {
|
|
7
|
+
class HybridRecording final : public HybridRecordingSpec {
|
|
8
|
+
public:
|
|
9
|
+
explicit HybridRecording(tracingcore::Config config);
|
|
10
|
+
~HybridRecording() override;
|
|
11
|
+
std::shared_ptr<HybridTraceSpanSpec> startSpan(const SpanOptions &options) override;
|
|
12
|
+
std::string recordSpan(const CompletedSpanOptions &options) override;
|
|
13
|
+
void mark(const MarkOptions &options) override;
|
|
14
|
+
void recordMetric(const MetricOptions &options) override;
|
|
15
|
+
std::shared_ptr<Promise<TracePage>> readEvents(const ReadOptions &options) override;
|
|
16
|
+
RecordingStats getStats() override;
|
|
17
|
+
void stop() override;
|
|
18
|
+
std::shared_ptr<Promise<std::string>> exportJson() override;
|
|
19
|
+
std::shared_ptr<Promise<std::string>> exportTraceEvents() override;
|
|
20
|
+
void startNativeSampling(const NativeSamplingOptions &options) override;
|
|
21
|
+
void stopNativeSampling() override;
|
|
22
|
+
size_t getExternalMemorySize() noexcept override;
|
|
23
|
+
void dispose() override;
|
|
24
|
+
|
|
25
|
+
private:
|
|
26
|
+
std::shared_ptr<tracingcore::Recorder> recorder_;
|
|
27
|
+
std::mutex samplerMutex_;
|
|
28
|
+
std::unique_ptr<tracingcore::NativeSampler> sampler_;
|
|
29
|
+
};
|
|
30
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#include "HybridTraceSpan.hpp"
|
|
2
|
+
#include "NativeConversions.hpp"
|
|
3
|
+
namespace margelo::nitro::nitrotracing {
|
|
4
|
+
HybridTraceSpan::HybridTraceSpan(std::shared_ptr<tracingcore::Recorder> r, uint64_t t)
|
|
5
|
+
: HybridObject(TAG), recorder_(std::move(r)), token_(t) {}
|
|
6
|
+
std::string HybridTraceSpan::getSpanId() { return recorder_->spanId(token_); }
|
|
7
|
+
bool HybridTraceSpan::getRecorded() { return token_ != 0; }
|
|
8
|
+
void HybridTraceSpan::end(SpanOutcome outcome) { recorder_->endSpan(token_, toCore(outcome)); }
|
|
9
|
+
void HybridTraceSpan::dispose() { recorder_->endSpan(token_, tracingcore::Outcome::Interrupted); }
|
|
10
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "HybridTraceSpanSpec.hpp"
|
|
3
|
+
#include "core/Recorder.hpp"
|
|
4
|
+
namespace margelo::nitro::nitrotracing {
|
|
5
|
+
class HybridTraceSpan final : public HybridTraceSpanSpec {
|
|
6
|
+
public:
|
|
7
|
+
HybridTraceSpan(std::shared_ptr<tracingcore::Recorder> recorder, uint64_t token);
|
|
8
|
+
std::string getSpanId() override;
|
|
9
|
+
bool getRecorded() override;
|
|
10
|
+
void end(SpanOutcome outcome) override;
|
|
11
|
+
void dispose() override;
|
|
12
|
+
|
|
13
|
+
private:
|
|
14
|
+
std::shared_ptr<tracingcore::Recorder> recorder_;
|
|
15
|
+
uint64_t token_;
|
|
16
|
+
};
|
|
17
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#include "HybridTracing.hpp"
|
|
2
|
+
#include "HermesSampling.hpp"
|
|
3
|
+
#include "HybridRecording.hpp"
|
|
4
|
+
#include "NativeConversions.hpp"
|
|
5
|
+
namespace margelo::nitro::nitrotracing {
|
|
6
|
+
std::shared_ptr<HybridRecordingSpec> HybridTracing::startRecording(const RecordingOptions &o) {
|
|
7
|
+
return std::make_shared<HybridRecording>(toCore(o));
|
|
8
|
+
}
|
|
9
|
+
bool HybridTracing::disableHermesSampling() { return nitrotracing::disableHermesSampling(); }
|
|
10
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "HybridTracingSpec.hpp"
|
|
3
|
+
namespace margelo::nitro::nitrotracing {
|
|
4
|
+
class HybridTracing final : public HybridTracingSpec {
|
|
5
|
+
public:
|
|
6
|
+
HybridTracing() : HybridObject(TAG) {}
|
|
7
|
+
std::shared_ptr<HybridRecordingSpec> startRecording(const RecordingOptions &options) override;
|
|
8
|
+
bool disableHermesSampling() override;
|
|
9
|
+
};
|
|
10
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#include "NativeConversions.hpp"
|
|
2
|
+
#include <cmath>
|
|
3
|
+
#include <stdexcept>
|
|
4
|
+
namespace margelo::nitro::nitrotracing {
|
|
5
|
+
uint64_t checkedInteger(double value, double maximum, const char *field) {
|
|
6
|
+
if (!std::isfinite(value) || value < 0 || value > maximum || std::floor(value) != value)
|
|
7
|
+
throw std::invalid_argument(std::string(field) + " must be a nonnegative integer within its documented limit");
|
|
8
|
+
return static_cast<uint64_t>(value);
|
|
9
|
+
}
|
|
10
|
+
tracingcore::Context toCore(const std::string &name, const std::string &id, const std::vector<TraceAttribute> &attrs) {
|
|
11
|
+
tracingcore::Context c{name, id, {}};
|
|
12
|
+
c.attributes.reserve(attrs.size());
|
|
13
|
+
for (const auto &a : attrs)
|
|
14
|
+
c.attributes.push_back({a.key, a.value});
|
|
15
|
+
return c;
|
|
16
|
+
}
|
|
17
|
+
tracingcore::Config toCore(const RecordingOptions &o) {
|
|
18
|
+
// Range-checked above, so the casts are safe where size_t is 32-bit (armeabi-v7a).
|
|
19
|
+
return {static_cast<size_t>(checkedInteger(o.maxEvents, 100000, "maxEvents")),
|
|
20
|
+
static_cast<size_t>(checkedInteger(o.maxBytes, 67108864, "maxBytes")),
|
|
21
|
+
static_cast<size_t>(checkedInteger(o.maxActiveSpans, 10000, "maxActiveSpans")), o.spanTimeoutMs};
|
|
22
|
+
}
|
|
23
|
+
tracingcore::Outcome toCore(SpanOutcome o) {
|
|
24
|
+
switch (o) {
|
|
25
|
+
case SpanOutcome::SUCCESS:
|
|
26
|
+
return tracingcore::Outcome::Success;
|
|
27
|
+
case SpanOutcome::ERROR:
|
|
28
|
+
return tracingcore::Outcome::Error;
|
|
29
|
+
case SpanOutcome::CANCELLED:
|
|
30
|
+
return tracingcore::Outcome::Cancelled;
|
|
31
|
+
case SpanOutcome::INTERRUPTED:
|
|
32
|
+
return tracingcore::Outcome::Interrupted;
|
|
33
|
+
}
|
|
34
|
+
throw std::invalid_argument("Invalid span outcome");
|
|
35
|
+
}
|
|
36
|
+
RecordingStats toNative(const tracingcore::Stats &s) {
|
|
37
|
+
return RecordingStats(s.sessionId, s.startedAtUnixMs, s.nowMs, s.recording, static_cast<double>(s.eventCount),
|
|
38
|
+
static_cast<double>(s.retainedBytes), static_cast<double>(s.droppedEvents),
|
|
39
|
+
static_cast<double>(s.activeSpans), static_cast<double>(s.droppedSpans),
|
|
40
|
+
static_cast<double>(s.droppedMarks), static_cast<double>(s.droppedMetrics));
|
|
41
|
+
}
|
|
42
|
+
TracePage toNative(const tracingcore::Page &page) {
|
|
43
|
+
TracePage p;
|
|
44
|
+
p.nextSequence = page.nextSequence;
|
|
45
|
+
p.earliestSequence = page.earliestSequence;
|
|
46
|
+
p.droppedEvents = page.droppedEvents;
|
|
47
|
+
for (const auto &e : page.events) {
|
|
48
|
+
std::vector<TraceAttribute> attrs;
|
|
49
|
+
attrs.reserve(e.context.attributes.size());
|
|
50
|
+
for (const auto &a : e.context.attributes)
|
|
51
|
+
attrs.emplace_back(a.key, a.value);
|
|
52
|
+
if (const auto *s = std::get_if<tracingcore::SpanData>(&e.data)) {
|
|
53
|
+
SpanOutcome outcome = SpanOutcome::SUCCESS;
|
|
54
|
+
switch (s->outcome) {
|
|
55
|
+
case tracingcore::Outcome::Success:
|
|
56
|
+
break;
|
|
57
|
+
case tracingcore::Outcome::Error:
|
|
58
|
+
outcome = SpanOutcome::ERROR;
|
|
59
|
+
break;
|
|
60
|
+
case tracingcore::Outcome::Cancelled:
|
|
61
|
+
outcome = SpanOutcome::CANCELLED;
|
|
62
|
+
break;
|
|
63
|
+
case tracingcore::Outcome::Interrupted:
|
|
64
|
+
outcome = SpanOutcome::INTERRUPTED;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
p.spans.emplace_back(s->spanId, s->parentSpanId, s->durationMs, outcome, e.sequence, e.timestampMs,
|
|
68
|
+
e.context.name, e.context.correlationId, attrs);
|
|
69
|
+
} else if (const auto *m = std::get_if<tracingcore::MetricData>(&e.data))
|
|
70
|
+
p.metrics.emplace_back(m->value, m->unit, e.sequence, e.timestampMs, e.context.name, e.context.correlationId,
|
|
71
|
+
attrs);
|
|
72
|
+
else
|
|
73
|
+
p.marks.emplace_back(e.sequence, e.timestampMs, e.context.name, e.context.correlationId, attrs);
|
|
74
|
+
}
|
|
75
|
+
return p;
|
|
76
|
+
}
|
|
77
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "RecordingOptions.hpp"
|
|
3
|
+
#include "RecordingStats.hpp"
|
|
4
|
+
#include "TraceAttribute.hpp"
|
|
5
|
+
#include "TracePage.hpp"
|
|
6
|
+
#include "core/TraceTypes.hpp"
|
|
7
|
+
namespace margelo::nitro::nitrotracing {
|
|
8
|
+
tracingcore::Context toCore(const std::string &name, const std::string &id,
|
|
9
|
+
const std::vector<TraceAttribute> &attributes);
|
|
10
|
+
tracingcore::Config toCore(const RecordingOptions &options);
|
|
11
|
+
tracingcore::Outcome toCore(SpanOutcome outcome);
|
|
12
|
+
TracePage toNative(const tracingcore::Page &page);
|
|
13
|
+
RecordingStats toNative(const tracingcore::Stats &stats);
|
|
14
|
+
uint64_t checkedInteger(double value, double maximum, const char *field);
|
|
15
|
+
} // namespace margelo::nitro::nitrotracing
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#include "NativeSampler.hpp"
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
#include <sys/resource.h>
|
|
5
|
+
#include <unistd.h>
|
|
6
|
+
#if defined(__APPLE__)
|
|
7
|
+
#include <mach/mach.h>
|
|
8
|
+
#endif
|
|
9
|
+
namespace margelo::nitro::tracingcore {
|
|
10
|
+
namespace {
|
|
11
|
+
// A frame taking 1.5x the display interval missed at least one deadline.
|
|
12
|
+
constexpr double slowFactor = 1.5;
|
|
13
|
+
// Android vitals and Sentry both treat >= 700 ms as a frozen frame.
|
|
14
|
+
constexpr double frozenMs = 700;
|
|
15
|
+
} // namespace
|
|
16
|
+
|
|
17
|
+
FrameMonitor &FrameMonitor::instance() {
|
|
18
|
+
static FrameMonitor monitor;
|
|
19
|
+
return monitor;
|
|
20
|
+
}
|
|
21
|
+
void FrameMonitor::setToggle(std::function<void(bool)> toggle) {
|
|
22
|
+
std::lock_guard lock(mutex_);
|
|
23
|
+
toggle_ = std::move(toggle);
|
|
24
|
+
}
|
|
25
|
+
bool FrameMonitor::acquire() {
|
|
26
|
+
std::function<void(bool)> toggle;
|
|
27
|
+
{
|
|
28
|
+
std::lock_guard lock(mutex_);
|
|
29
|
+
if (!toggle_)
|
|
30
|
+
return false;
|
|
31
|
+
if (users_++ == 0) {
|
|
32
|
+
last_ = 0;
|
|
33
|
+
window_ = {};
|
|
34
|
+
toggle = toggle_;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (toggle)
|
|
38
|
+
toggle(true);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
void FrameMonitor::release() {
|
|
42
|
+
std::function<void(bool)> toggle;
|
|
43
|
+
{
|
|
44
|
+
std::lock_guard lock(mutex_);
|
|
45
|
+
if (users_ > 0 && --users_ == 0)
|
|
46
|
+
toggle = toggle_;
|
|
47
|
+
}
|
|
48
|
+
if (toggle)
|
|
49
|
+
toggle(false);
|
|
50
|
+
}
|
|
51
|
+
void FrameMonitor::onFrame(double timestamp, double expected) {
|
|
52
|
+
std::lock_guard lock(mutex_);
|
|
53
|
+
if (last_ > 0 && timestamp > last_) {
|
|
54
|
+
const double gapMs = (timestamp - last_) * 1000;
|
|
55
|
+
// Without a platform interval, the shortest recent gap approximates the vsync period.
|
|
56
|
+
if (expected <= 0)
|
|
57
|
+
minGap_ = minGap_ <= 0 ? gapMs : std::min(minGap_, gapMs);
|
|
58
|
+
const double intervalMs = expected > 0 ? expected * 1000 : minGap_;
|
|
59
|
+
++window_.frames;
|
|
60
|
+
window_.intervalMs = intervalMs;
|
|
61
|
+
window_.maxGapMs = std::max(window_.maxGapMs, gapMs);
|
|
62
|
+
if (gapMs >= frozenMs)
|
|
63
|
+
++window_.frozen;
|
|
64
|
+
else if (intervalMs > 0 && gapMs > intervalMs * slowFactor)
|
|
65
|
+
++window_.slow;
|
|
66
|
+
}
|
|
67
|
+
last_ = timestamp;
|
|
68
|
+
}
|
|
69
|
+
FrameMonitor::Window FrameMonitor::take() {
|
|
70
|
+
std::lock_guard lock(mutex_);
|
|
71
|
+
const auto window = window_;
|
|
72
|
+
window_ = {};
|
|
73
|
+
return window;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
double NativeSampler::processCpuMs() {
|
|
77
|
+
rusage usage{};
|
|
78
|
+
if (getrusage(RUSAGE_SELF, &usage) != 0)
|
|
79
|
+
return 0;
|
|
80
|
+
const auto ms = [](timeval t) { return t.tv_sec * 1000.0 + t.tv_usec / 1000.0; };
|
|
81
|
+
return ms(usage.ru_utime) + ms(usage.ru_stime);
|
|
82
|
+
}
|
|
83
|
+
double NativeSampler::processMemoryBytes() {
|
|
84
|
+
#if defined(__APPLE__)
|
|
85
|
+
task_vm_info_data_t info{};
|
|
86
|
+
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
|
|
87
|
+
if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &count) == KERN_SUCCESS)
|
|
88
|
+
return static_cast<double>(info.phys_footprint);
|
|
89
|
+
return 0;
|
|
90
|
+
#else
|
|
91
|
+
long pages = 0, resident = 0;
|
|
92
|
+
FILE *file = std::fopen("/proc/self/statm", "r");
|
|
93
|
+
if (!file)
|
|
94
|
+
return 0;
|
|
95
|
+
const bool read = std::fscanf(file, "%ld %ld", &pages, &resident) == 2;
|
|
96
|
+
std::fclose(file);
|
|
97
|
+
return read ? static_cast<double>(resident) * static_cast<double>(sysconf(_SC_PAGESIZE)) : 0;
|
|
98
|
+
#endif
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
NativeSampler::NativeSampler(std::shared_ptr<Recorder> recorder, double intervalMs, bool frames)
|
|
102
|
+
: recorder_(std::move(recorder)), intervalMs_(intervalMs), frames_(frames && FrameMonitor::instance().acquire()),
|
|
103
|
+
thread_([this] { run(); }) {}
|
|
104
|
+
NativeSampler::~NativeSampler() {
|
|
105
|
+
{
|
|
106
|
+
std::lock_guard lock(mutex_);
|
|
107
|
+
stopping_ = true;
|
|
108
|
+
}
|
|
109
|
+
wake_.notify_all();
|
|
110
|
+
thread_.join();
|
|
111
|
+
if (frames_)
|
|
112
|
+
FrameMonitor::instance().release();
|
|
113
|
+
}
|
|
114
|
+
void NativeSampler::run() {
|
|
115
|
+
double lastWall = Recorder::monotonicMilliseconds();
|
|
116
|
+
double lastCpu = processCpuMs();
|
|
117
|
+
if (frames_)
|
|
118
|
+
FrameMonitor::instance().take(); // Discard frames from before this window.
|
|
119
|
+
std::unique_lock lock(mutex_);
|
|
120
|
+
while (!wake_.wait_for(lock, std::chrono::duration<double, std::milli>(intervalMs_), [this] { return stopping_; })) {
|
|
121
|
+
lock.unlock();
|
|
122
|
+
const double wall = Recorder::monotonicMilliseconds();
|
|
123
|
+
const double cpu = processCpuMs();
|
|
124
|
+
const double windowMs = wall - lastWall;
|
|
125
|
+
const auto context = [&](const char *name) {
|
|
126
|
+
return Context{name, "", {{"source", "native"}, {"windowMs", std::to_string(static_cast<int>(windowMs))}}};
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
if (windowMs > 0)
|
|
130
|
+
// Percent of one core: a busy multi-threaded process can exceed 100.
|
|
131
|
+
recorder_->metric(context("process.cpu"), std::max(0.0, (cpu - lastCpu) / windowMs * 100), "%");
|
|
132
|
+
if (const double memory = processMemoryBytes(); memory > 0)
|
|
133
|
+
recorder_->metric(context("process.memory"), memory / (1024 * 1024), "MB");
|
|
134
|
+
if (frames_) {
|
|
135
|
+
const auto w = FrameMonitor::instance().take();
|
|
136
|
+
// Paused display callbacks (background, locked screen) produce no frames: report nothing.
|
|
137
|
+
if (w.frames > 1 && windowMs > 0) {
|
|
138
|
+
recorder_->metric(context("ui.fps"), w.frames * 1000 / windowMs, "fps");
|
|
139
|
+
recorder_->metric(context("ui.frame_gap.max"), w.maxGapMs, "ms");
|
|
140
|
+
recorder_->metric(context("ui.frames.slow"), static_cast<double>(w.slow), "count");
|
|
141
|
+
recorder_->metric(context("ui.frames.frozen"), static_cast<double>(w.frozen), "count");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (...) {
|
|
145
|
+
return; // Recording stopped or was cleared; the owner joins this thread.
|
|
146
|
+
}
|
|
147
|
+
lastWall = wall;
|
|
148
|
+
lastCpu = cpu;
|
|
149
|
+
lock.lock();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} // namespace margelo::nitro::tracingcore
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "Recorder.hpp"
|
|
3
|
+
#include <condition_variable>
|
|
4
|
+
#include <functional>
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <thread>
|
|
8
|
+
namespace margelo::nitro::tracingcore {
|
|
9
|
+
/** Aggregates main-thread frame callbacks between sampler windows. Process-wide. */
|
|
10
|
+
class FrameMonitor final {
|
|
11
|
+
public:
|
|
12
|
+
struct Window {
|
|
13
|
+
uint64_t frames = 0, slow = 0, frozen = 0;
|
|
14
|
+
double maxGapMs = 0, intervalMs = 0;
|
|
15
|
+
};
|
|
16
|
+
static FrameMonitor &instance();
|
|
17
|
+
/** Platform layer: start/stop display callbacks on the main thread. */
|
|
18
|
+
void setToggle(std::function<void(bool)> toggle);
|
|
19
|
+
/** Reference-counted so concurrent recordings share one callback source. */
|
|
20
|
+
bool acquire();
|
|
21
|
+
void release();
|
|
22
|
+
/** Seconds on a monotonic clock; expected frame interval, or 0 to infer it. */
|
|
23
|
+
void onFrame(double timestampSeconds, double expectedIntervalSeconds);
|
|
24
|
+
Window take();
|
|
25
|
+
|
|
26
|
+
private:
|
|
27
|
+
std::mutex mutex_;
|
|
28
|
+
std::function<void(bool)> toggle_;
|
|
29
|
+
int users_ = 0;
|
|
30
|
+
double last_ = 0, minGap_ = 0;
|
|
31
|
+
Window window_;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Background thread writing CPU, memory and frame windows into a recorder. */
|
|
35
|
+
class NativeSampler final {
|
|
36
|
+
public:
|
|
37
|
+
NativeSampler(std::shared_ptr<Recorder> recorder, double intervalMs, bool frames);
|
|
38
|
+
~NativeSampler();
|
|
39
|
+
NativeSampler(const NativeSampler &) = delete;
|
|
40
|
+
NativeSampler &operator=(const NativeSampler &) = delete;
|
|
41
|
+
/** CPU time used by this process across all threads, in milliseconds. */
|
|
42
|
+
static double processCpuMs();
|
|
43
|
+
/** iOS physical footprint or Android/Linux resident set, in bytes; 0 when unavailable. */
|
|
44
|
+
static double processMemoryBytes();
|
|
45
|
+
|
|
46
|
+
private:
|
|
47
|
+
void run();
|
|
48
|
+
std::shared_ptr<Recorder> recorder_;
|
|
49
|
+
double intervalMs_;
|
|
50
|
+
bool frames_;
|
|
51
|
+
std::mutex mutex_;
|
|
52
|
+
std::condition_variable wake_;
|
|
53
|
+
bool stopping_ = false;
|
|
54
|
+
std::thread thread_;
|
|
55
|
+
};
|
|
56
|
+
} // namespace margelo::nitro::tracingcore
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#include "Recorder.hpp"
|
|
2
|
+
#include "Retention.hpp"
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <atomic>
|
|
5
|
+
#include <chrono>
|
|
6
|
+
#include <cmath>
|
|
7
|
+
#include <stdexcept>
|
|
8
|
+
namespace margelo::nitro::tracingcore {
|
|
9
|
+
namespace {
|
|
10
|
+
std::atomic<uint64_t> nextSession{0};
|
|
11
|
+
}
|
|
12
|
+
double Recorder::monotonicMilliseconds() {
|
|
13
|
+
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
14
|
+
}
|
|
15
|
+
Recorder::Recorder(Config c, Clock clock)
|
|
16
|
+
: config_(c), clock_(std::move(clock)), origin_(clock_()),
|
|
17
|
+
wallOrigin_(
|
|
18
|
+
std::chrono::duration<double, std::milli>(std::chrono::system_clock::now().time_since_epoch()).count()),
|
|
19
|
+
sessionId_(std::to_string(static_cast<uint64_t>(wallOrigin_)) + "-" + std::to_string(++nextSession)) {
|
|
20
|
+
if (c.maxEvents < 1 || c.maxEvents > 100000 || c.maxBytes < 1024 || c.maxBytes > 67108864 || c.maxActiveSpans < 1 ||
|
|
21
|
+
c.maxActiveSpans > 10000 || !std::isfinite(c.spanTimeoutMs) || c.spanTimeoutMs < 1)
|
|
22
|
+
throw std::invalid_argument("Invalid recording limits: events 1..100000, bytes 1024..67108864, active spans "
|
|
23
|
+
"1..10000, finite timeout >=1ms");
|
|
24
|
+
}
|
|
25
|
+
double Recorder::now() const { return clock_() - origin_; }
|
|
26
|
+
void Recorder::requireRecording() const {
|
|
27
|
+
if (!recording_)
|
|
28
|
+
throw std::runtime_error("Recording has stopped");
|
|
29
|
+
}
|
|
30
|
+
std::string Recorder::spanId(uint64_t token) const {
|
|
31
|
+
return token == 0 ? "" : sessionId_ + ":" + std::to_string(token);
|
|
32
|
+
}
|
|
33
|
+
namespace {
|
|
34
|
+
bool isMetric(const Event &e) { return std::holds_alternative<MetricData>(e.data); }
|
|
35
|
+
} // namespace
|
|
36
|
+
void Recorder::drop(const Event &e) {
|
|
37
|
+
++dropped_;
|
|
38
|
+
if (std::holds_alternative<SpanData>(e.data))
|
|
39
|
+
++droppedSpans_;
|
|
40
|
+
else if (std::holds_alternative<MarkData>(e.data))
|
|
41
|
+
++droppedMarks_;
|
|
42
|
+
else
|
|
43
|
+
++droppedMetrics_;
|
|
44
|
+
}
|
|
45
|
+
void Recorder::evict() {
|
|
46
|
+
metrics_ -= isMetric(events_.front());
|
|
47
|
+
bytes_ -= eventBytes(events_.front());
|
|
48
|
+
drop(events_.front());
|
|
49
|
+
events_.pop_front();
|
|
50
|
+
}
|
|
51
|
+
bool Recorder::reserve(size_t bytes) {
|
|
52
|
+
while (!events_.empty() && bytes_ + bytes > config_.maxBytes)
|
|
53
|
+
evict();
|
|
54
|
+
return bytes_ + bytes <= config_.maxBytes;
|
|
55
|
+
}
|
|
56
|
+
void Recorder::append(Event e) {
|
|
57
|
+
e.sequence = ++sequence_;
|
|
58
|
+
const auto size = eventBytes(e);
|
|
59
|
+
if (size > config_.maxBytes) {
|
|
60
|
+
drop(e);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Periodic samples may fill at most half the buffer, so they replace older samples
|
|
64
|
+
// instead of evicting spans and marks from long sessions. Metric series are rolling
|
|
65
|
+
// windows by design, so replacing a sample does not count as lost history.
|
|
66
|
+
if (isMetric(e) && metrics_ >= std::max<size_t>(1, config_.maxEvents / 2)) {
|
|
67
|
+
const auto oldest = std::find_if(events_.begin(), events_.end(), isMetric);
|
|
68
|
+
bytes_ -= eventBytes(*oldest);
|
|
69
|
+
events_.erase(oldest);
|
|
70
|
+
--metrics_;
|
|
71
|
+
}
|
|
72
|
+
while (!events_.empty() && events_.size() >= config_.maxEvents)
|
|
73
|
+
evict();
|
|
74
|
+
if (!reserve(size)) {
|
|
75
|
+
drop(e);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
bytes_ += size;
|
|
79
|
+
metrics_ += isMetric(e);
|
|
80
|
+
events_.push_back(std::move(e));
|
|
81
|
+
}
|
|
82
|
+
uint64_t Recorder::startSpan(Context c, std::string parent) {
|
|
83
|
+
validateContext(c);
|
|
84
|
+
if (parent.size() > sessionId_.size() + 8 + 128 || (!parent.empty() && parent.rfind(sessionId_ + ":", 0) != 0))
|
|
85
|
+
throw std::invalid_argument("Parent span must belong to this recording");
|
|
86
|
+
std::lock_guard lock(mutex_);
|
|
87
|
+
requireRecording();
|
|
88
|
+
const double time = now();
|
|
89
|
+
expire(time);
|
|
90
|
+
const size_t size = sizeof(Active) + contextBytes(c) + parent.capacity() + 64;
|
|
91
|
+
if (active_.size() >= config_.maxActiveSpans || size > config_.maxBytes || !reserve(size)) {
|
|
92
|
+
++dropped_;
|
|
93
|
+
++droppedSpans_;
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
const auto token = ++token_;
|
|
97
|
+
active_.emplace(token, Active{time, std::move(c), std::move(parent), size});
|
|
98
|
+
bytes_ += size;
|
|
99
|
+
return token;
|
|
100
|
+
}
|
|
101
|
+
void Recorder::finish(std::map<uint64_t, Active>::iterator it, Outcome outcome, double time) {
|
|
102
|
+
auto active = std::move(it->second);
|
|
103
|
+
const auto token = it->first;
|
|
104
|
+
bytes_ -= active.bytes;
|
|
105
|
+
active_.erase(it);
|
|
106
|
+
append(Event{0, active.start, std::move(active.context),
|
|
107
|
+
SpanData{spanId(token), std::move(active.parent), time - active.start, outcome}});
|
|
108
|
+
}
|
|
109
|
+
void Recorder::expire(double time) {
|
|
110
|
+
while (!active_.empty() && time - active_.begin()->second.start >= config_.spanTimeoutMs)
|
|
111
|
+
finish(active_.begin(), Outcome::Interrupted, active_.begin()->second.start + config_.spanTimeoutMs);
|
|
112
|
+
}
|
|
113
|
+
std::string Recorder::recordSpan(Context c, std::string parent, double start, double duration, Outcome outcome,
|
|
114
|
+
std::string sourceId, std::string sourceParent) {
|
|
115
|
+
validateContext(c);
|
|
116
|
+
if (!std::isfinite(start) || start < 0 || !std::isfinite(duration) || duration < 0 ||
|
|
117
|
+
!std::isfinite(start + duration))
|
|
118
|
+
throw std::invalid_argument("Imported span requires finite nonnegative start and duration");
|
|
119
|
+
if (parent.size() > sessionId_.size() + 8 + 128 || (!parent.empty() && parent.rfind(sessionId_ + ":", 0) != 0))
|
|
120
|
+
throw std::invalid_argument("Parent span must belong to this recording");
|
|
121
|
+
std::lock_guard lock(mutex_);
|
|
122
|
+
requireRecording();
|
|
123
|
+
expire(now());
|
|
124
|
+
if (sourceId.size() > 128 || sourceParent.size() > 128 || (!sourceParent.empty() && sourceId.empty()))
|
|
125
|
+
throw std::invalid_argument("Source span IDs must be <=128 bytes and a source parent requires a source ID");
|
|
126
|
+
const auto id = sourceId.empty() ? spanId(++token_) : sessionId_ + ":source:" + sourceId;
|
|
127
|
+
if (!sourceParent.empty())
|
|
128
|
+
parent = sessionId_ + ":source:" + sourceParent;
|
|
129
|
+
append(Event{0, start, std::move(c), SpanData{id, std::move(parent), duration, outcome}});
|
|
130
|
+
return id;
|
|
131
|
+
}
|
|
132
|
+
void Recorder::endSpan(uint64_t token, Outcome outcome) {
|
|
133
|
+
if (token == 0)
|
|
134
|
+
return;
|
|
135
|
+
std::lock_guard lock(mutex_);
|
|
136
|
+
const double time = now();
|
|
137
|
+
expire(time);
|
|
138
|
+
auto it = active_.find(token);
|
|
139
|
+
if (it != active_.end())
|
|
140
|
+
finish(it, outcome, time);
|
|
141
|
+
}
|
|
142
|
+
void Recorder::mark(Context c, std::optional<double> timestamp) {
|
|
143
|
+
if (timestamp && (!std::isfinite(*timestamp) || *timestamp < 0))
|
|
144
|
+
throw std::invalid_argument("Mark timestamp must be finite and nonnegative");
|
|
145
|
+
validateContext(c);
|
|
146
|
+
std::lock_guard lock(mutex_);
|
|
147
|
+
requireRecording();
|
|
148
|
+
const double time = now();
|
|
149
|
+
expire(time);
|
|
150
|
+
append(Event{0, timestamp.value_or(time), std::move(c), MarkData{}});
|
|
151
|
+
}
|
|
152
|
+
void Recorder::metric(Context c, double value, std::string unit, std::optional<double> timestamp) {
|
|
153
|
+
if (timestamp && (!std::isfinite(*timestamp) || *timestamp < 0))
|
|
154
|
+
throw std::invalid_argument("Metric timestamp must be finite and nonnegative");
|
|
155
|
+
validateContext(c);
|
|
156
|
+
if (!std::isfinite(value) || unit.empty() || unit.size() > 64)
|
|
157
|
+
throw std::invalid_argument("Metric needs finite value and a 1-64 byte unit");
|
|
158
|
+
std::lock_guard lock(mutex_);
|
|
159
|
+
requireRecording();
|
|
160
|
+
const double time = now();
|
|
161
|
+
expire(time);
|
|
162
|
+
append(Event{0, timestamp.value_or(time), std::move(c), MetricData{value, std::move(unit)}});
|
|
163
|
+
}
|
|
164
|
+
Page Recorder::read(uint64_t after, size_t limit) {
|
|
165
|
+
if (limit < 1 || limit > 1000)
|
|
166
|
+
throw std::invalid_argument("Page limit must be 1..1000");
|
|
167
|
+
std::lock_guard lock(mutex_);
|
|
168
|
+
expire(now());
|
|
169
|
+
Page p{{}, after, events_.empty() ? sequence_ + 1 : events_.front().sequence, dropped_};
|
|
170
|
+
auto it = std::upper_bound(events_.begin(), events_.end(), after,
|
|
171
|
+
[](uint64_t seq, const Event &event) { return seq < event.sequence; });
|
|
172
|
+
for (; it != events_.end() && p.events.size() < limit; ++it) {
|
|
173
|
+
p.events.push_back(*it);
|
|
174
|
+
p.nextSequence = it->sequence;
|
|
175
|
+
}
|
|
176
|
+
return p;
|
|
177
|
+
}
|
|
178
|
+
Stats Recorder::statsAt(double time) const {
|
|
179
|
+
return {sessionId_, wallOrigin_, time, recording_, events_.size(), bytes_,
|
|
180
|
+
dropped_, active_.size(), droppedSpans_, droppedMarks_, droppedMetrics_};
|
|
181
|
+
}
|
|
182
|
+
Stats Recorder::stats() {
|
|
183
|
+
std::lock_guard lock(mutex_);
|
|
184
|
+
const auto time = now();
|
|
185
|
+
expire(time);
|
|
186
|
+
return statsAt(time);
|
|
187
|
+
}
|
|
188
|
+
Snapshot Recorder::snapshot() {
|
|
189
|
+
std::lock_guard lock(mutex_);
|
|
190
|
+
const auto time = now();
|
|
191
|
+
expire(time);
|
|
192
|
+
return {statsAt(time),
|
|
193
|
+
{std::vector<Event>(events_.begin(), events_.end()), sequence_,
|
|
194
|
+
events_.empty() ? sequence_ + 1 : events_.front().sequence, dropped_}};
|
|
195
|
+
}
|
|
196
|
+
void Recorder::stop() {
|
|
197
|
+
std::lock_guard lock(mutex_);
|
|
198
|
+
if (!recording_)
|
|
199
|
+
return;
|
|
200
|
+
const auto time = now();
|
|
201
|
+
expire(time);
|
|
202
|
+
recording_ = false;
|
|
203
|
+
while (!active_.empty())
|
|
204
|
+
finish(active_.begin(), Outcome::Interrupted, time);
|
|
205
|
+
}
|
|
206
|
+
void Recorder::clear() {
|
|
207
|
+
std::lock_guard lock(mutex_);
|
|
208
|
+
recording_ = false;
|
|
209
|
+
active_.clear();
|
|
210
|
+
events_.clear();
|
|
211
|
+
bytes_ = 0;
|
|
212
|
+
metrics_ = 0;
|
|
213
|
+
}
|
|
214
|
+
size_t Recorder::memorySize() const {
|
|
215
|
+
std::lock_guard lock(mutex_);
|
|
216
|
+
return sizeof(Recorder) + bytes_;
|
|
217
|
+
}
|
|
218
|
+
} // namespace margelo::nitro::tracingcore
|