Skip to content

Commit eba680c

Browse files
Gasoonjiafacebook-github-bot
authored andcommitted
introduce file_data_sink (#8921)
Summary: This diff introduces a new data sink called `file_data_sink` to the Executorch runtime to have a data logging pipeline into file. Differential Revision: D70541550
1 parent e2201c5 commit eba680c

File tree

5 files changed

+326
-11
lines changed

5 files changed

+326
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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_sinks/file_data_sink.h>
10+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
11+
#include <cstdio> // For FILE operations
12+
13+
using ::executorch::runtime::Error;
14+
using ::executorch::runtime::Result;
15+
16+
namespace executorch {
17+
namespace etdump {
18+
19+
FileDataSink::FileDataSink(FileDataSink&& other) noexcept
20+
: file_(other.file_), total_written_bytes_(other.total_written_bytes_) {
21+
other.file_ = nullptr;
22+
}
23+
24+
Result<FileDataSink> FileDataSink::create(const char* file_path) {
25+
// Open the file and get the file pointer
26+
FILE* file = fopen(file_path, "wb");
27+
if (!file) {
28+
// Return an error if the file cannot be accessed or created
29+
return Error::AccessFailed;
30+
}
31+
32+
// Return the successfully created FileDataSink
33+
return FileDataSink(file);
34+
}
35+
36+
FileDataSink::~FileDataSink() {
37+
// Close the file
38+
close();
39+
}
40+
41+
Result<size_t> FileDataSink::write(const void* ptr, size_t size) {
42+
if (!file_) {
43+
ET_LOG(Error, "File not open, unable to write.");
44+
return Error::AccessFailed;
45+
}
46+
47+
size_t offset = total_written_bytes_;
48+
49+
if (size == 0) {
50+
// No data to write, return current offset
51+
return offset;
52+
}
53+
54+
size_t written = fwrite(ptr, 1, size, file_);
55+
if (written != size) {
56+
ET_LOG(Error, "Write failed: wrote %zu bytes of %zu", written, size);
57+
return Error::Internal;
58+
}
59+
60+
total_written_bytes_ += written;
61+
return offset;
62+
}
63+
64+
size_t FileDataSink::get_used_bytes() const {
65+
return total_written_bytes_;
66+
}
67+
68+
void FileDataSink::close() {
69+
if (file_) {
70+
fclose(file_);
71+
file_ = nullptr;
72+
}
73+
}
74+
75+
} // namespace etdump
76+
} // namespace executorch
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
#pragma once
10+
11+
#include <executorch/devtools/etdump/data_sinks/data_sink_base.h>
12+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
13+
#include <cstdio> // For FILE operations
14+
15+
namespace executorch {
16+
namespace etdump {
17+
18+
/**
19+
* FileDataSink is a concrete implementation of the DataSinkBase class,
20+
* designed to facilitate the direct writing of data to a file. It is
21+
* particularly useful for scenarios where immediate data storage is
22+
* required, such as logging or streaming data to a file for real-time
23+
* analysis. The class manages file operations, including opening, writing,
24+
* and closing the file, while handling potential errors during these
25+
* operations.
26+
*/
27+
28+
class FileDataSink : public DataSinkBase {
29+
public:
30+
/**
31+
* Creates a FileDataSink with a given file path.
32+
*
33+
* @param[in] file_path The path to the file for writing data.
34+
* @return A Result object containing either:
35+
* - A FileDataSink object if succees, or
36+
* - AccessFailed Error when the file cannot be accessed or created
37+
*/
38+
static ::executorch::runtime::Result<FileDataSink> create(
39+
const char* file_path);
40+
41+
/**
42+
* Destructor that closes the file.
43+
*/
44+
~FileDataSink() override;
45+
46+
// Delete copy constructor and copy assignment operator
47+
FileDataSink(const FileDataSink&) = delete;
48+
FileDataSink& operator=(const FileDataSink&) = delete;
49+
50+
FileDataSink(FileDataSink&& other) noexcept;
51+
FileDataSink& operator=(FileDataSink&& other) = default;
52+
53+
/**
54+
* Writes data directly to the file.
55+
*
56+
* This function does not perform any alignment, and will overwrite
57+
* any existing data in the file.
58+
*
59+
* @param[in] ptr A pointer to the data to be written into the file.
60+
* @param[in] size The size of the data in bytes.
61+
* @return A Result object containing either:
62+
* - The offset of the starting location of the data within the
63+
* file, or
64+
* - AccessFailedError if the file has been closed.
65+
* - InternalError if the os write operation fails.
66+
*/
67+
::executorch::runtime::Result<size_t> write(const void* ptr, size_t size)
68+
override;
69+
70+
/**
71+
* Gets the number of bytes currently written to the file.
72+
*
73+
* @return The amount of data currently stored in bytes.
74+
*/
75+
size_t get_used_bytes() const override;
76+
77+
/**
78+
* Closes the file, if it is open.
79+
*/
80+
void close();
81+
82+
private:
83+
/**
84+
* Constructs a FileDataSink with a given file pointer.
85+
*
86+
* @param[in] file A valid file pointer for writing data.
87+
*/
88+
explicit FileDataSink(FILE* file) : file_(file), total_written_bytes_(0) {}
89+
90+
FILE* file_;
91+
size_t total_written_bytes_;
92+
};
93+
94+
} // namespace etdump
95+
} // namespace executorch

devtools/etdump/data_sinks/targets.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ def define_common_targets():
4747
)
4848

4949
define_data_sink_target("buffer_data_sink", aten_suffix)
50+
define_data_sink_target("file_data_sink", aten_suffix)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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_sinks/file_data_sink.h>
10+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
11+
#include <executorch/runtime/platform/runtime.h>
12+
#include <gtest/gtest.h>
13+
#include <stdio.h> // tmpnam(), remove()
14+
#include <fstream>
15+
16+
using namespace ::testing;
17+
using ::executorch::etdump::FileDataSink;
18+
using ::executorch::runtime::Error;
19+
using ::executorch::runtime::Result;
20+
21+
class FileDataSinkTest : public ::testing::Test {
22+
protected:
23+
void SetUp() override {
24+
// Initialize the runtime environment
25+
torch::executor::runtime_init();
26+
27+
// Define the file path for testing
28+
std::array<char, L_tmpnam> buf;
29+
const char* ret = std::tmpnam(buf.data());
30+
ASSERT_NE(ret, nullptr) << "Could not generate temp file";
31+
buf[L_tmpnam - 1] = '\0';
32+
file_path_ = std::string(buf.data()) + "-executorch-testing";
33+
}
34+
35+
void TearDown() override {
36+
// Remove the test file
37+
std::remove(file_path_.c_str());
38+
}
39+
40+
std::string file_path_;
41+
};
42+
43+
TEST_F(FileDataSinkTest, CreationExpectFail) {
44+
// Create a FileDataSink instance with a valid file path
45+
Result<FileDataSink> success = FileDataSink::create(file_path_.c_str());
46+
ASSERT_TRUE(success.ok());
47+
48+
// Try to create another FileDataSink instance with an invalid file path
49+
Result<FileDataSink> fail_with_invalid_file_path = FileDataSink::create("");
50+
ASSERT_EQ(fail_with_invalid_file_path.error(), Error::AccessFailed);
51+
}
52+
53+
TEST_F(FileDataSinkTest, WriteDataToFile) {
54+
const char* data = "Hello, World!";
55+
size_t data_size = strlen(data);
56+
57+
// Create a FileDataSink instance
58+
Result<FileDataSink> result = FileDataSink::create(file_path_.c_str());
59+
ASSERT_TRUE(result.ok());
60+
61+
FileDataSink* data_sink = &result.get();
62+
63+
// Write data to the file
64+
Result<size_t> write_result = data_sink->write(data, data_size);
65+
ASSERT_TRUE(write_result.ok());
66+
67+
size_t used_bytes = data_sink->get_used_bytes();
68+
EXPECT_EQ(used_bytes, data_size);
69+
70+
data_sink->close();
71+
72+
// Expect fail if write again after close
73+
Result<size_t> write_result_after_close = data_sink->write(data, data_size);
74+
ASSERT_EQ(write_result_after_close.error(), Error::AccessFailed);
75+
76+
// Verify the file contents
77+
std::ifstream file(file_path_, std::ios::binary);
78+
file.seekg(0, std::ios::end);
79+
size_t file_size = file.tellg();
80+
file.seekg(0, std::ios::beg);
81+
EXPECT_EQ(file_size, used_bytes);
82+
83+
// Read the file content and verify it matches the original data
84+
std::vector<char> file_content(file_size);
85+
file.read(file_content.data(), file_size);
86+
file.close();
87+
88+
EXPECT_EQ(std::memcmp(file_content.data(), data, data_size), 0);
89+
}
90+
91+
TEST_F(FileDataSinkTest, WriteMultipleDataAndCheckOffsets) {
92+
const char* data1 = "Accelerate";
93+
const char* data2 = "Core";
94+
const char* data3 = "Experience";
95+
size_t data1_size = strlen(data1);
96+
size_t data2_size = strlen(data2);
97+
size_t data3_size = strlen(data3);
98+
99+
// Create a FileDataSink instance
100+
Result<FileDataSink> result = FileDataSink::create(file_path_.c_str());
101+
ASSERT_TRUE(result.ok());
102+
FileDataSink* data_sink = &result.get();
103+
104+
// Write multiple data chunks and check offsets
105+
Result<size_t> offset1 = data_sink->write(data1, data1_size);
106+
ASSERT_TRUE(offset1.ok());
107+
EXPECT_EQ(offset1.get(), 0);
108+
109+
Result<size_t> offset2 = data_sink->write(data2, data2_size);
110+
ASSERT_TRUE(offset2.ok());
111+
EXPECT_EQ(offset2.get(), data1_size);
112+
113+
Result<size_t> offset3 = data_sink->write(data3, data3_size);
114+
ASSERT_TRUE(offset3.ok());
115+
EXPECT_EQ(offset3.get(), data1_size + data2_size + data3_size);
116+
size_t used_bytes = data_sink->get_used_bytes();
117+
EXPECT_EQ(used_bytes, data1_size + data2_size);
118+
119+
data_sink->close();
120+
121+
// Verify the file contents
122+
std::ifstream file(file_path_, std::ios::binary);
123+
file.seekg(0, std::ios::end);
124+
size_t file_size = file.tellg();
125+
file.seekg(0, std::ios::beg);
126+
EXPECT_EQ(file_size, used_bytes);
127+
128+
// Read the file content
129+
std::vector<char> file_content(file_size);
130+
file.read(file_content.data(), file_size);
131+
file.close();
132+
133+
// Verify each data chunk in the file using offsets
134+
EXPECT_EQ(
135+
std::memcmp(file_content.data() + offset1.get(), data1, data1_size), 0);
136+
EXPECT_EQ(
137+
std::memcmp(file_content.data() + offset2.get(), data2, data2_size), 0);
138+
EXPECT_EQ(
139+
std::memcmp(file_content.data() + offset3.get(), data3, data3_size), 0);
140+
}
+14-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")
22

3-
def define_common_targets():
4-
"""Defines targets that should be shared between fbcode and xplat.
5-
6-
The directory containing this targets.bzl file should also contain both
7-
TARGETS and BUCK files that call this function.
8-
"""
9-
10-
3+
def define_data_sink_test(data_sink_name):
114
runtime.cxx_test(
12-
name = "buffer_data_sink_test",
5+
name = data_sink_name + "_test",
136
srcs = [
14-
"buffer_data_sink_test.cpp",
7+
data_sink_name + "_test.cpp",
158
],
169
deps = [
17-
"//executorch/devtools/etdump/data_sinks:buffer_data_sink",
10+
"//executorch/devtools/etdump/data_sinks:" + data_sink_name,
1811
"//executorch/runtime/core/exec_aten/testing_util:tensor_util",
1912
],
2013
)
14+
15+
def define_common_targets():
16+
"""Defines targets that should be shared between fbcode and xplat.
17+
18+
The directory containing this targets.bzl file should also contain both
19+
TARGETS and BUCK files that call this function.
20+
"""
21+
22+
define_data_sink_test("buffer_data_sink")
23+
define_data_sink_test("file_data_sink")

0 commit comments

Comments
 (0)