Skip to content

Commit e71f360

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. Reviewed By: tarun292 Differential Revision: D70541550
1 parent 9ab5c15 commit e71f360

File tree

5 files changed

+325
-11
lines changed

5 files changed

+325
-11
lines changed
Lines changed: 76 additions & 0 deletions
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 <cstdio> // For FILE operations
11+
12+
using ::executorch::runtime::Error;
13+
using ::executorch::runtime::Result;
14+
15+
namespace executorch {
16+
namespace etdump {
17+
18+
FileDataSink::FileDataSink(FileDataSink&& other) noexcept
19+
: file_(other.file_), total_written_bytes_(other.total_written_bytes_) {
20+
other.file_ = nullptr;
21+
}
22+
23+
Result<FileDataSink> FileDataSink::create(const char* file_path) {
24+
// Open the file and get the file pointer
25+
FILE* file = fopen(file_path, "wb");
26+
if (!file) {
27+
// Return an error if the file cannot be accessed or created
28+
ET_LOG(Error, "File %s cannot be accessed or created.", file_path);
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
Lines changed: 95 additions & 0 deletions
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

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