Skip to content

Commit 4767fb9

Browse files
committed
Add the ability to append to the result json file for wallclock benchmarks (AI Generated)
1 parent 57faaec commit 4767fb9

1 file changed

Lines changed: 135 additions & 30 deletions

File tree

core/src/walltime.cpp

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -59,30 +59,8 @@ static std::string escape_backslashes(const std::string &input) {
5959
return output;
6060
}
6161

62-
static void write_codspeed_benchmarks_to_json(
63-
const std::vector<CodspeedWalltimeBenchmark> &benchmarks) {
62+
static std::string serialize_benchmark_objects(const std::vector<CodspeedWalltimeBenchmark> &benchmarks) {
6463
std::ostringstream oss;
65-
66-
std::string creator_name = "codspeed-cpp";
67-
std::string creator_version = CODSPEED_VERSION;
68-
#ifdef _WIN32
69-
auto creator_pid = _getpid();
70-
#else
71-
pid_t creator_pid = getpid();
72-
#endif
73-
std::string instrument_type = "walltime";
74-
75-
oss << "{\n";
76-
oss << " \"creator\": {\n";
77-
oss << " \"name\": \"" << creator_name << "\",\n";
78-
oss << " \"version\": \"" << creator_version << "\",\n";
79-
oss << " \"pid\": " << creator_pid << "\n";
80-
oss << " },\n";
81-
oss << " \"instrument\": {\n";
82-
oss << " \"type\": \"" << instrument_type << "\"\n";
83-
oss << " },\n";
84-
oss << " \"benchmarks\": [\n";
85-
8664
for (size_t i = 0; i < benchmarks.size(); ++i) {
8765
const auto &benchmark = benchmarks[i];
8866
const auto &stats = benchmark.stats;
@@ -122,9 +100,16 @@ static void write_codspeed_benchmarks_to_json(
122100
}
123101
oss << "\n";
124102
}
103+
return oss.str();
104+
}
125105

126-
oss << " ]\n";
127-
oss << "}";
106+
// Extracts the file path calculation and directory creation to avoid redundancy
107+
static std::string get_codspeed_results_file_path() {
108+
#ifdef _WIN32
109+
auto creator_pid = _getpid();
110+
#else
111+
pid_t creator_pid = getpid();
112+
#endif
128113

129114
// Determine the directory path
130115
std::string profile_folder = safe_getenv("CODSPEED_PROFILE_FOLDER");
@@ -135,25 +120,145 @@ static void write_codspeed_benchmarks_to_json(
135120
if (!std::filesystem::exists(results_path)) {
136121
if (!std::filesystem::create_directories(results_path)) {
137122
std::cerr << "Failed to create directory: " << results_path << std::endl;
138-
return;
123+
return "";
139124
}
140125
}
141126

142127
// Create the file path
143128
std::ostringstream file_path;
144129
file_path << results_path.string() << "/" << creator_pid << ".json";
130+
return file_path.str();
131+
}
132+
133+
static void write_codspeed_benchmarks_to_json(
134+
const std::vector<CodspeedWalltimeBenchmark> &benchmarks) {
135+
std::ostringstream oss;
136+
137+
std::string creator_name = "codspeed-cpp";
138+
std::string creator_version = CODSPEED_VERSION;
139+
#ifdef _WIN32
140+
auto creator_pid = _getpid();
141+
#else
142+
pid_t creator_pid = getpid();
143+
#endif
144+
std::string instrument_type = "walltime";
145+
146+
oss << "{\n";
147+
oss << " \"creator\": {\n";
148+
oss << " \"name\": \"" << creator_name << "\",\n";
149+
oss << " \"version\": \"" << creator_version << "\",\n";
150+
oss << " \"pid\": " << creator_pid << "\n";
151+
oss << " },\n";
152+
oss << " \"instrument\": {\n";
153+
oss << " \"type\": \"" << instrument_type << "\"\n";
154+
oss << " },\n";
155+
oss << " \"benchmarks\": [\n";
156+
157+
oss << serialize_benchmark_objects(benchmarks);
158+
159+
oss << " ]\n";
160+
oss << "}";
161+
162+
std::string file_path_str = get_codspeed_results_file_path();
163+
if (file_path_str.empty()) return;
145164

146165
// Write to file
147-
std::ofstream out_file(file_path.str());
166+
std::ofstream out_file(file_path_str);
148167
if (out_file.is_open()) {
149168
out_file << oss.str();
150169
out_file.close();
151-
std::cout << "JSON written to " << file_path.str() << std::endl;
170+
std::cout << "JSON written to " << file_path_str << std::endl;
152171
} else {
153-
std::cerr << "Unable to open file " << file_path.str() << std::endl;
172+
std::cerr << "Unable to open file " << file_path_str << std::endl;
154173
}
155174
}
156175

176+
static void append_codspeed_benchmarks_to_json(
177+
const std::vector<CodspeedWalltimeBenchmark> &benchmarks) {
178+
179+
if (benchmarks.empty()) return;
180+
181+
std::string file_path_str = get_codspeed_results_file_path();
182+
if (file_path_str.empty()) return;
183+
184+
// Condition: If file is missing or entirely empty, generate fresh
185+
if (!std::filesystem::exists(file_path_str) || std::filesystem::file_size(file_path_str) == 0) {
186+
write_codspeed_benchmarks_to_json(benchmarks);
187+
return;
188+
}
189+
190+
// Open in read/write binary mode to prevent Windows newline conversion from messing with stream offsets
191+
std::fstream file(file_path_str, std::ios::in | std::ios::out | std::ios::binary);
192+
if (!file) {
193+
std::cerr << "Failed to open file for appending: " << file_path_str << std::endl;
194+
return;
195+
}
196+
197+
// Back up from the EOF until we strip away '}', ']', and whitespace
198+
file.seekp(0, std::ios::end);
199+
std::streampos pos = file.tellp();
200+
201+
int state = 0;
202+
bool array_was_empty = false;
203+
std::streampos truncation_pos = 0;
204+
205+
// Read backwards to safely find where the trailing array structural markers close
206+
while (pos > 0) {
207+
pos -= 1;
208+
file.seekg(pos);
209+
char ch;
210+
file.get(ch);
211+
212+
if (std::isspace(static_cast<unsigned char>(ch))) {
213+
continue;
214+
}
215+
216+
// State 0: Find the outer root object closure '}'
217+
if (state == 0 && ch == '}') {
218+
state = 1;
219+
continue;
220+
}
221+
222+
// State 1: Find the array list closure ']'
223+
if (state == 1 && ch == ']') {
224+
state = 2;
225+
// Truncate right here! This preserves whatever character came right before the array bracket ']'
226+
truncation_pos = pos;
227+
continue;
228+
}
229+
230+
// State 2: Check what the last character inside the array was
231+
if (state == 2) {
232+
if (ch == '[') {
233+
array_was_empty = true;
234+
}
235+
break;
236+
}
237+
}
238+
239+
if (state < 2) {
240+
std::cerr << "Failed to find valid JSON structural endings to safely append." << std::endl;
241+
return;
242+
}
243+
244+
file.seekp(truncation_pos);
245+
246+
if (!array_was_empty) {
247+
file << ",\n"; // Commas are mandatory between pre-existing elements
248+
} else {
249+
file << "\n";
250+
}
251+
252+
// Inject the new items
253+
file << serialize_benchmark_objects(benchmarks);
254+
255+
// Re-close the JSON root safely (matches old string generation spaces precisely)
256+
file << " ]\n}";
257+
258+
file.flush();
259+
std::filesystem::resize_file(file_path_str, file.tellp());
260+
}
261+
157262
BenchmarkStats compute_benchmark_stats(
158263
const RawWalltimeBenchmark &raw_benchmark) {
159264
assert(raw_benchmark.iters_per_round.size() ==
@@ -245,7 +350,7 @@ void generate_codspeed_walltime_report(
245350
codspeed_walltime_benchmarks.push_back(codspeed_benchmark);
246351
}
247352

248-
write_codspeed_benchmarks_to_json(codspeed_walltime_benchmarks);
353+
append_codspeed_benchmarks_to_json(codspeed_walltime_benchmarks);
249354
}
250355

251356
} // namespace codspeed

0 commit comments

Comments
 (0)