Skip to content

Commit fc82d82

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 Differential Revision: [D69583422](https://our.internmc.facebook.com/intern/diff/D69583422/) ghstack-source-id: 266455279
1 parent bc497a0 commit fc82d82

File tree

8 files changed

+301
-14
lines changed

8 files changed

+301
-14
lines changed

devtools/etdump/data_sink.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
2+
3+
#include <executorch/devtools/etdump/data_sink.h>
4+
#include <executorch/devtools/etdump/utils.h>
5+
6+
namespace executorch {
7+
namespace etdump {
8+
9+
size_t DataSink::write_tensor(const executorch::aten::Tensor& tensor) {
10+
if (tensor.nbytes() == 0) {
11+
return static_cast<size_t>(-1);
12+
}
13+
uint8_t* offset_ptr =
14+
internal::alignPointer(debug_buffer_.data() + offset_, 64);
15+
offset_ = (offset_ptr - debug_buffer_.data()) + tensor.nbytes();
16+
ET_CHECK_MSG(
17+
offset_ <= debug_buffer_.size(),
18+
"Ran out of space to store tensor data.");
19+
memcpy(offset_ptr, tensor.const_data_ptr(), tensor.nbytes());
20+
return (size_t)(offset_ptr - debug_buffer_.data());
21+
}
22+
23+
size_t DataSink::get_storage_size() const {
24+
return debug_buffer_.size();
25+
}
26+
27+
size_t DataSink::get_used_bytes() const {
28+
return offset_;
29+
}
30+
31+
} // namespace etdump
32+
} // namespace executorch

devtools/etdump/data_sink.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 DataSink : public DataSinkBase {
13+
public:
14+
explicit DataSink(::executorch::runtime::Span<uint8_t> buffer)
15+
: debug_buffer_(buffer), offset_(0) {}
16+
17+
size_t write_tensor(const executorch::aten::Tensor& tensor) override;
18+
size_t get_storage_size() const override;
19+
size_t get_used_bytes() const override;
20+
21+
private:
22+
::executorch::runtime::Span<uint8_t> debug_buffer_;
23+
// The offset of the next available location in the debug storage
24+
size_t offset_;
25+
};
26+
27+
} // namespace etdump
28+
} // namespace executorch

devtools/etdump/data_sink_base.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 <cstdlib>
7+
8+
namespace executorch {
9+
namespace etdump {
10+
11+
/**
12+
* DataSinkBase is an abstract class that users can inherit and implement
13+
* to customize the storage and management of debug data in ETDumpGen. This
14+
* class provides an basic and essential interface for writing tensor data to a
15+
* user-defined storage, retrieving storage capacity, and tracking the amount of
16+
* data stored.
17+
*/
18+
class DataSinkBase {
19+
public:
20+
/**
21+
* Virtual destructor to ensure proper cleanup of derived classes.
22+
*/
23+
virtual ~DataSinkBase() = default;
24+
/**
25+
* Write tensor data into the debug storage. This method should be implemented
26+
* by derived classes to handle the specifics of data storage.
27+
*
28+
* @param[in] tensor The tensor data to be written into the storage.
29+
* @return The offset of the starting location of the tensor data within the
30+
* debug storage, which will be recorded in corresponding tensor
31+
* metadata of ETDump.
32+
*/
33+
virtual size_t write_tensor(const executorch::aten::Tensor& tensor) = 0;
34+
/**
35+
* Get the maximum capacity of the debug storage in bytes.
36+
*
37+
* @return The total size of the debug storage.
38+
*/
39+
virtual size_t get_storage_size() const = 0;
40+
/**
41+
* Get the number of bytes currently used in the debug storage.
42+
*
43+
* @return The amount of data currently stored in bytes.
44+
*/
45+
virtual size_t get_used_bytes() const = 0;
46+
};
47+
48+
} // namespace etdump
49+
} // 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: 48 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 = "data_sink" + aten_suffix,
120+
headers = [
121+
"data_sink.h",
122+
],
123+
srcs = [
124+
"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 = [
@@ -103,9 +149,11 @@ def define_common_targets():
103149
],
104150
deps = [
105151
"//executorch/runtime/platform:platform",
152+
":utils",
106153
],
107154
exported_deps = [
108155
":etdump_schema_flatcc",
156+
":utils",
109157
"//executorch/runtime/core:event_tracer" + aten_suffix,
110158
"//executorch/runtime/core/exec_aten/util:scalar_type_util" + aten_suffix,
111159
],
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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/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 DataSinkTest : 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::DataSink>(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::DataSink> data_sink_;
41+
};
42+
43+
TEST_F(DataSinkTest, StorageSizeCheck) {
44+
EXPECT_EQ(data_sink_->get_storage_size(), buffer_size_);
45+
}
46+
47+
TEST_F(DataSinkTest, WriteOneTensorAndCheckData) {
48+
TensorFactory<ScalarType::Float> tf;
49+
Tensor tensor = tf.make({1, 4}, {1.0, 2.0, 3.0, 4.0});
50+
51+
size_t offset = data_sink_->write_tensor(tensor);
52+
EXPECT_NE(offset, static_cast<size_t>(-1));
53+
54+
// Check that the data in the buffer matches the tensor data
55+
const float* buffer_data =
56+
reinterpret_cast<const float*>(buffer_.data() + offset);
57+
for (size_t i = 0; i < tensor.numel(); ++i) {
58+
EXPECT_EQ(buffer_data[i], tensor.const_data_ptr<float>()[i]);
59+
}
60+
}
61+
62+
TEST_F(DataSinkTest, WriteMultiTensorsAndCheckData) {
63+
TensorFactory<ScalarType::Float> tf;
64+
std::vector<Tensor> tensors = {
65+
tf.make({1, 4}, {1.0, 2.0, 3.0, 4.0}),
66+
tf.make({1, 4}, {5.0, 6.0, 7.0, 8.0})};
67+
size_t offset = 0;
68+
for (const auto& tensor : tensors) {
69+
offset = data_sink_->write_tensor(tensor);
70+
EXPECT_NE(offset, static_cast<size_t>(-1));
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+
80+
TEST_F(DataSinkTest, PointerAlignmentCheck) {
81+
TensorFactory<ScalarType::Float> tf;
82+
Tensor tensor = tf.make({1, 4}, {1.0, 2.0, 3.0, 4.0});
83+
size_t offset = data_sink_->write_tensor(tensor);
84+
EXPECT_NE(offset, static_cast<size_t>(-1));
85+
// Check that the offset pointer is 64-byte aligned
86+
const uint8_t* offset_ptr = buffer_.data() + offset;
87+
EXPECT_EQ(reinterpret_cast<uintptr_t>(offset_ptr) % 64, 0);
88+
}
89+
90+
TEST_F(DataSinkTest, WriteUntilOverflow) {
91+
TensorFactory<ScalarType::Float> tf;
92+
Tensor tensor = tf.zeros({1, 8}); // Large tensor to fill the buffer
93+
94+
// Write tensors until we run out of space
95+
for (size_t i = 0; i < 2; i++) {
96+
data_sink_->write_tensor(tensor);
97+
}
98+
99+
// Attempting to write another tensor should raise an error
100+
ET_EXPECT_DEATH(
101+
data_sink_->write_tensor(tensor),
102+
"Ran out of space to store tensor data.");
103+
}

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 = "data_sink_test",
25+
srcs = [
26+
"data_sink_test.cpp",
27+
],
28+
deps = [
29+
"//executorch/devtools/etdump:data_sink",
30+
"//executorch/runtime/core/exec_aten/testing_util:tensor_util",
31+
],
32+
)

devtools/etdump/utils.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
6+
#pragma once
7+
8+
namespace executorch {
9+
namespace etdump {
10+
namespace internal {
11+
12+
inline uint8_t* alignPointer(void* ptr, size_t alignment) {
13+
intptr_t addr = reinterpret_cast<intptr_t>(ptr);
14+
if ((addr & (alignment - 1)) == 0) {
15+
// Already aligned.
16+
return reinterpret_cast<uint8_t*>(ptr);
17+
}
18+
addr = (addr | (alignment - 1)) + 1;
19+
return reinterpret_cast<uint8_t*>(addr);
20+
}
21+
22+
} // namespace internal
23+
} // namespace etdump
24+
} // namespace executorch

0 commit comments

Comments
 (0)