@@ -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,146 @@ 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+ if (!std::filesystem::exists (file_path_str) || std::filesystem::file_size (file_path_str) == 0 ) {
185+ write_codspeed_benchmarks_to_json (benchmarks);
186+ return ;
187+ }
188+
189+ std::fstream file (file_path_str, std::ios::in | std::ios::out | std::ios::binary);
190+ if (!file) {
191+ std::cerr << " Failed to open file for appending: " << file_path_str << std::endl;
192+ return ;
193+ }
194+
195+ file.seekp (0 , std::ios::end);
196+ std::streampos pos = file.tellp ();
197+
198+ int state = 0 ;
199+ bool array_was_empty = false ;
200+ std::streampos truncation_pos = 0 ;
201+
202+ while (pos > 0 ) {
203+ pos -= 1 ;
204+ file.seekg (pos);
205+ char ch;
206+ file.get (ch);
207+
208+ // State 0: Skip trailing whitespace outside the root object, find '}'
209+ if (state == 0 ) {
210+ if (std::isspace (static_cast <unsigned char >(ch))) continue ;
211+ if (ch == ' }' ) {
212+ state = 1 ;
213+ continue ;
214+ }
215+ }
216+
217+ // State 1: Skip whitespace between '}' and ']', find ']'
218+ if (state == 1 ) {
219+ if (std::isspace (static_cast <unsigned char >(ch))) continue ;
220+ if (ch == ' ]' ) {
221+ state = 2 ;
222+ continue ;
223+ }
224+ }
225+
226+ // State 2: We are inside the array. Skip whitespace to find the actual content end
227+ if (state == 2 ) {
228+ if (std::isspace (static_cast <unsigned char >(ch))) {
229+ continue ;
230+ }
231+
232+ if (ch == ' [' ) {
233+ array_was_empty = true ;
234+ }
235+
236+ truncation_pos = pos + std::streamoff (1 );
237+ break ;
238+ }
239+ }
240+
241+ if (state < 2 ) {
242+ std::cerr << " Failed to find valid JSON structural endings to safely append." << std::endl;
243+ return ;
244+ }
245+
246+ file.seekp (truncation_pos);
247+
248+ if (!array_was_empty) {
249+ file << " ,\n " ;
250+ } else {
251+ file << " \n " ;
252+ }
253+
254+ // Inject the new items
255+ file << serialize_benchmark_objects (benchmarks);
256+
257+ file << " ]\n }" ;
258+
259+ file.flush ();
260+ std::filesystem::resize_file (file_path_str, file.tellp ());
261+ }
262+
157263BenchmarkStats compute_benchmark_stats (
158264 const RawWalltimeBenchmark &raw_benchmark) {
159265 assert (raw_benchmark.iters_per_round .size () ==
@@ -245,7 +351,7 @@ void generate_codspeed_walltime_report(
245351 codspeed_walltime_benchmarks.push_back (codspeed_benchmark);
246352 }
247353
248- write_codspeed_benchmarks_to_json (codspeed_walltime_benchmarks);
354+ append_codspeed_benchmarks_to_json (codspeed_walltime_benchmarks);
249355}
250356
251357} // namespace codspeed
0 commit comments