@@ -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+
157262BenchmarkStats 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