Skip to content

Commit 245f933

Browse files
committed
[devtool] introduce datasink class to etdump
Pull Request resolved: #8496 this diff introduce datasink class, the class for managing the customized debug data storage pipeline. Detials can be found in https://docs.google.com/document/d/1y_m32mKdj-OgLcLUz9TKhBW3PC3bBDYSBbeAH544EfM/edit?tab=t.0 ghstack-source-id: 266594898 Differential Revision: [D69583422](https://our.internmc.facebook.com/intern/diff/D69583422/)
1 parent bc497a0 commit 245f933

File tree

8 files changed

+348
-14
lines changed

8 files changed

+348
-14
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
2+
3+
#include <executorch/devtools/etdump/buffer_data_sink.h>
4+
#include <executorch/devtools/etdump/utils.h>
5+
6+
namespace executorch {
7+
namespace etdump {
8+
9+
Result<size_t> BufferDataSink::write(const void* ptr, size_t length) {
10+
if (length == 0) {
11+
return static_cast<size_t>(-1);
12+
}
13+
uint8_t* offset_ptr =
14+
internal::alignPointer(debug_buffer_.data() + offset_, 64);
15+
16+
// Zero out the padding between data blobs.
17+
size_t n_zero_pad = offset_ptr - debug_buffer_.data() - offset_;
18+
memset(debug_buffer_.data() + offset_, 0, n_zero_pad);
19+
20+
offset_ = (offset_ptr - debug_buffer_.data()) + length;
21+
ET_CHECK_MSG(
22+
offset_ <= debug_buffer_.size(),
23+
"Ran out of space to store tensor data.");
24+
memcpy(offset_ptr, ptr, length);
25+
return (size_t)(offset_ptr - debug_buffer_.data());
26+
}
27+
28+
Result<size_t> BufferDataSink::write_tensor(
29+
const executorch::aten::Tensor& tensor) {
30+
return write(tensor.const_data_ptr(), tensor.nbytes());
31+
}
32+
33+
Result<size_t> BufferDataSink::get_storage_size() const {
34+
return debug_buffer_.size();
35+
}
36+
37+
size_t BufferDataSink::get_used_bytes() const {
38+
return offset_;
39+
}
40+
41+
} // namespace etdump
42+
} // namespace executorch

devtools/etdump/buffer_data_sink.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
2+
3+
#pragma once
4+
5+
#include <executorch/devtools/etdump/data_sink_base.h>
6+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
7+
#include <executorch/runtime/core/span.h>
8+
9+
namespace executorch {
10+
namespace etdump {
11+
12+
class BufferDataSink : public DataSinkBase {
13+
public:
14+
explicit BufferDataSink(::executorch::runtime::Span<uint8_t> buffer)
15+
: debug_buffer_(buffer), offset_(0) {}
16+
17+
Result<size_t> write(const void* ptr, size_t length) override;
18+
Result<size_t> write_tensor(const executorch::aten::Tensor& tensor);
19+
Result<size_t> get_storage_size() const override;
20+
size_t get_used_bytes() const override;
21+
22+
private:
23+
::executorch::runtime::Span<uint8_t> debug_buffer_;
24+
// The offset of the next available location in the debug storage
25+
size_t offset_;
26+
};
27+
28+
} // namespace etdump
29+
} // namespace executorch

devtools/etdump/data_sink_base.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
2+
3+
#pragma once
4+
5+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
6+
#include <executorch/runtime/core/result.h>
7+
8+
using ::executorch::runtime::Result;
9+
10+
namespace executorch {
11+
namespace etdump {
12+
13+
/**
14+
* DataSinkBase is an abstract class that users can inherit and implement
15+
* to customize the storage and management of debug data in ETDumpGen. This
16+
* class provides an basic and essential interface for writing datablob to a
17+
* user-defined storage, retrieving storage capacity, and tracking the amount of
18+
* data stored.
19+
*/
20+
class DataSinkBase {
21+
public:
22+
/**
23+
* Virtual destructor to ensure proper cleanup of derived classes.
24+
*/
25+
virtual ~DataSinkBase() = default;
26+
/**
27+
* Write data into the debug storage. This method should be implemented
28+
* by derived classes to handle the specifics of data storage.
29+
*
30+
* @param[in] ptr A pointer to the data to be written into the storage.
31+
* @param[in] length The size of the data in bytes.
32+
* @return The offset of the starting location of the data within the
33+
* debug storage, which will be recorded in the corresponding
34+
* metadata of ETDump.
35+
*/
36+
virtual Result<size_t> write(const void* ptr, size_t length) = 0;
37+
/**
38+
* Get the maximum capacity of the debug storage in bytes.
39+
* Should return Error::NotSupported if the capacity is not available
40+
* (e.g. unbounded storage like internet or file)
41+
*
42+
* @return The total size of the debug storage.
43+
*/
44+
virtual Result<size_t> get_storage_size() const = 0;
45+
/**
46+
* Get the number of bytes currently used in the debug storage.
47+
*
48+
* @return The amount of data currently stored in bytes.
49+
*/
50+
virtual size_t get_used_bytes() const = 0;
51+
};
52+
53+
} // namespace etdump
54+
} // namespace executorch

devtools/etdump/etdump_flatcc.cpp

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <executorch/devtools/etdump/emitter.h>
1414
#include <executorch/devtools/etdump/etdump_schema_flatcc_builder.h>
1515
#include <executorch/devtools/etdump/etdump_schema_flatcc_reader.h>
16+
#include <executorch/devtools/etdump/utils.h>
1617
#include <executorch/runtime/core/exec_aten/exec_aten.h>
1718
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
1819
#include <executorch/runtime/platform/assert.h>
@@ -94,16 +95,6 @@ etdump_Tensor_ref_t add_tensor_entry(
9495
return etdump_Tensor_end(builder_);
9596
}
9697

97-
static uint8_t* alignPointer(void* ptr, size_t alignment) {
98-
intptr_t addr = reinterpret_cast<intptr_t>(ptr);
99-
if ((addr & (alignment - 1)) == 0) {
100-
// Already aligned.
101-
return reinterpret_cast<uint8_t*>(ptr);
102-
}
103-
addr = (addr | (alignment - 1)) + 1;
104-
return reinterpret_cast<uint8_t*>(addr);
105-
}
106-
10798
} // namespace
10899

109100
// Constructor implementation
@@ -113,9 +104,10 @@ ETDumpGen::ETDumpGen(Span<uint8_t> buffer) {
113104
// Initialize the flatcc builder_ using the buffer and buffer size.
114105

115106
if (buffer.data() != nullptr) {
116-
builder_ = (struct flatcc_builder*)alignPointer(buffer.data(), 64);
117-
uintptr_t buffer_with_builder =
118-
(uintptr_t)alignPointer(builder_ + sizeof(struct flatcc_builder), 64);
107+
builder_ =
108+
(struct flatcc_builder*)internal::alignPointer(buffer.data(), 64);
109+
uintptr_t buffer_with_builder = (uintptr_t)internal::alignPointer(
110+
builder_ + sizeof(struct flatcc_builder), 64);
119111
size_t builder_size =
120112
(size_t)(buffer_with_builder - (uintptr_t)buffer.data());
121113
size_t min_buf_size = max_alloc_buf_size + builder_size;
@@ -513,7 +505,7 @@ size_t ETDumpGen::copy_tensor_to_debug_buffer(executorch::aten::Tensor tensor) {
513505
return static_cast<size_t>(-1);
514506
}
515507
uint8_t* offset_ptr =
516-
alignPointer(debug_buffer_.data() + debug_buffer_offset_, 64);
508+
internal::alignPointer(debug_buffer_.data() + debug_buffer_offset_, 64);
517509
debug_buffer_offset_ = (offset_ptr - debug_buffer_.data()) + tensor.nbytes();
518510
ET_CHECK_MSG(
519511
debug_buffer_offset_ <= debug_buffer_.size(),

devtools/etdump/targets.bzl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,54 @@ def define_common_targets():
8787
exported_external_deps = ["flatccrt"],
8888
)
8989

90+
runtime.cxx_library(
91+
name = "utils",
92+
srcs = [],
93+
exported_headers = [
94+
"utils.h",
95+
],
96+
visibility = [
97+
98+
],
99+
)
100+
90101
for aten_mode in (True, False):
91102
aten_suffix = "_aten" if aten_mode else ""
103+
104+
runtime.cxx_library(
105+
name = "data_sink_base" + aten_suffix,
106+
exported_headers = [
107+
"data_sink_base.h",
108+
],
109+
exported_deps = [
110+
"//executorch/runtime/core/exec_aten/util:scalar_type_util" + aten_suffix,
111+
],
112+
visibility = [
113+
"//executorch/...",
114+
"@EXECUTORCH_CLIENTS",
115+
],
116+
)
117+
118+
runtime.cxx_library(
119+
name = "buffer_data_sink" + aten_suffix,
120+
headers = [
121+
"buffer_data_sink.h",
122+
],
123+
srcs = [
124+
"buffer_data_sink.cpp",
125+
],
126+
deps = [
127+
":utils",
128+
],
129+
exported_deps = [
130+
"//executorch/runtime/core/exec_aten:lib" + aten_suffix,
131+
":data_sink_base" + aten_suffix,
132+
],
133+
visibility = [
134+
"//executorch/...",
135+
"@EXECUTORCH_CLIENTS",
136+
],
137+
)
92138
runtime.cxx_library(
93139
name = "etdump_flatcc" + aten_suffix,
94140
srcs = [
@@ -106,6 +152,7 @@ def define_common_targets():
106152
],
107153
exported_deps = [
108154
":etdump_schema_flatcc",
155+
":utils",
109156
"//executorch/runtime/core:event_tracer" + aten_suffix,
110157
"//executorch/runtime/core/exec_aten/util:scalar_type_util" + aten_suffix,
111158
],
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <executorch/devtools/etdump/buffer_data_sink.h>
10+
#include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
11+
#include <executorch/runtime/core/span.h>
12+
#include <executorch/runtime/platform/runtime.h>
13+
#include <executorch/test/utils/DeathTest.h>
14+
#include <gtest/gtest.h>
15+
16+
using namespace ::testing;
17+
using executorch::aten::ScalarType;
18+
using executorch::aten::Tensor;
19+
using ::executorch::runtime::Span;
20+
using torch::executor::testing::TensorFactory;
21+
22+
class BufferDataSinkTest : public ::testing::Test {
23+
protected:
24+
void SetUp() override {
25+
torch::executor::runtime_init();
26+
// Allocate a small buffer for testing
27+
buffer_size_ = 128; // Small size for testing
28+
buffer_ptr_ = malloc(buffer_size_);
29+
buffer_ = Span<uint8_t>(static_cast<uint8_t*>(buffer_ptr_), buffer_size_);
30+
data_sink_ = std::make_unique<executorch::etdump::BufferDataSink>(buffer_);
31+
}
32+
33+
void TearDown() override {
34+
free(buffer_ptr_);
35+
}
36+
37+
size_t buffer_size_;
38+
void* buffer_ptr_;
39+
Span<uint8_t> buffer_;
40+
std::unique_ptr<executorch::etdump::BufferDataSink> data_sink_;
41+
};
42+
43+
TEST_F(BufferDataSinkTest, StorageSizeCheck) {
44+
Result<size_t> ret = data_sink_->get_storage_size();
45+
if (!ret.ok()) {
46+
ET_LOG(
47+
Info,
48+
"Get storage size failed with 0x%" PRIx32,
49+
(unsigned int)ret.error());
50+
}
51+
size_t storage_size = ret.get();
52+
EXPECT_EQ(storage_size, buffer_size_);
53+
}
54+
55+
TEST_F(BufferDataSinkTest, WriteOneTensorAndCheckData) {
56+
TensorFactory<ScalarType::Float> tf;
57+
Tensor tensor = tf.make({1, 4}, {1.0, 2.0, 3.0, 4.0});
58+
59+
Result<size_t> ret = data_sink_->write_tensor(tensor);
60+
if (!ret.ok()) {
61+
ET_LOG(
62+
Info,
63+
"Write tensor into debug storage failed with 0x%" PRIx32,
64+
(unsigned int)ret.error());
65+
}
66+
67+
size_t offset = ret.get();
68+
69+
EXPECT_NE(offset, static_cast<size_t>(-1));
70+
71+
// Check that the data in the buffer matches the tensor data
72+
const float* buffer_data =
73+
reinterpret_cast<const float*>(buffer_.data() + offset);
74+
for (size_t i = 0; i < tensor.numel(); ++i) {
75+
EXPECT_EQ(buffer_data[i], tensor.const_data_ptr<float>()[i]);
76+
}
77+
}
78+
79+
TEST_F(BufferDataSinkTest, WriteMultiTensorsAndCheckData) {
80+
TensorFactory<ScalarType::Float> tf;
81+
std::vector<Tensor> tensors = {
82+
tf.make({1, 4}, {1.0, 2.0, 3.0, 4.0}),
83+
tf.make({1, 4}, {5.0, 6.0, 7.0, 8.0})};
84+
for (const auto& tensor : tensors) {
85+
Result<size_t> ret = data_sink_->write_tensor(tensor);
86+
if (!ret.ok()) {
87+
ET_LOG(
88+
Info,
89+
"Write tensor into debug storage failed with 0x%" PRIx32,
90+
(unsigned int)ret.error());
91+
}
92+
93+
size_t offset = ret.get();
94+
EXPECT_NE(offset, static_cast<size_t>(-1));
95+
// Check that the data in the buffer matches the tensor data
96+
const float* buffer_data =
97+
reinterpret_cast<const float*>(buffer_.data() + offset);
98+
for (size_t i = 0; i < tensor.numel(); ++i) {
99+
EXPECT_EQ(buffer_data[i], tensor.const_data_ptr<float>()[i]);
100+
}
101+
}
102+
}
103+
104+
TEST_F(BufferDataSinkTest, PointerAlignmentCheck) {
105+
TensorFactory<ScalarType::Float> tf;
106+
Tensor tensor = tf.make({1, 4}, {1.0, 2.0, 3.0, 4.0});
107+
Result<size_t> ret = data_sink_->write_tensor(tensor);
108+
if (!ret.ok()) {
109+
ET_LOG(
110+
Info,
111+
"Write tensor into debug storage failed with 0x%" PRIx32,
112+
(unsigned int)ret.error());
113+
}
114+
115+
size_t offset = ret.get();
116+
EXPECT_NE(offset, static_cast<size_t>(-1));
117+
// Check that the offset pointer is 64-byte aligned
118+
const uint8_t* offset_ptr = buffer_.data() + offset;
119+
EXPECT_EQ(reinterpret_cast<uintptr_t>(offset_ptr) % 64, 0);
120+
}
121+
122+
TEST_F(BufferDataSinkTest, WriteUntilOverflow) {
123+
TensorFactory<ScalarType::Float> tf;
124+
Tensor tensor = tf.zeros({1, 8}); // Large tensor to fill the buffer
125+
126+
// Write tensors until we run out of space
127+
for (size_t i = 0; i < 2; i++) {
128+
data_sink_->write_tensor(tensor);
129+
}
130+
131+
// Attempting to write another tensor should raise an error
132+
ET_EXPECT_DEATH(
133+
data_sink_->write_tensor(tensor),
134+
"Ran out of space to store tensor data.");
135+
}

devtools/etdump/tests/targets.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,14 @@ def define_common_targets():
1919
"//executorch/runtime/core/exec_aten/testing_util:tensor_util",
2020
],
2121
)
22+
23+
runtime.cxx_test(
24+
name = "buffer_data_sink_test",
25+
srcs = [
26+
"buffer_data_sink_test.cpp",
27+
],
28+
deps = [
29+
"//executorch/devtools/etdump:buffer_data_sink",
30+
"//executorch/runtime/core/exec_aten/testing_util:tensor_util",
31+
],
32+
)

0 commit comments

Comments
 (0)