@@ -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 ;
@@ -118,40 +96,134 @@ static void write_codspeed_benchmarks_to_json(
11896 oss << " }" ;
11997
12098 if (i < benchmarks.size () - 1 ) {
121- oss << " ," ;
99+ oss << " ,\n " ;
122100 }
123- oss << " \n " ;
124101 }
102+ return oss.str ();
103+ }
125104
126- oss << " ]\n " ;
127- oss << " }" ;
128-
129- // Determine the directory path
105+ static std::filesystem::path get_results_path () {
130106 std::string profile_folder = safe_getenv (" CODSPEED_PROFILE_FOLDER" );
131107 std::string directory = profile_folder.empty () ? " ." : profile_folder;
108+ return std::filesystem::path (directory) / " results" / " benchmarks.json" ;
109+ }
110+
111+ static void write_codspeed_benchmarks_to_json (
112+ const std::vector<CodspeedWalltimeBenchmark> &benchmarks) {
113+
114+ std::filesystem::path file_path = get_results_path ();
115+ std::filesystem::create_directories (file_path.parent_path ());
116+
117+ std::ofstream out (file_path, std::ios::trunc);
118+ if (!out) {
119+ std::cerr << " Failed to open file for writing: " << file_path << std::endl;
120+ return ;
121+ }
132122
133- // Create the results directory if it does not exist
134- std::filesystem::path results_path = directory + " /results" ;
135- if (!std::filesystem::exists (results_path)) {
136- if (!std::filesystem::create_directories (results_path)) {
137- std::cerr << " Failed to create directory: " << results_path << std::endl;
138- return ;
123+ std::string creator_name = " codspeed-cpp" ;
124+ std::string creator_version = CODSPEED_VERSION;
125+ #ifdef _WIN32
126+ auto creator_pid = _getpid ();
127+ #else
128+ pid_t creator_pid = getpid ();
129+ #endif
130+ std::string instrument_type = " walltime" ;
131+
132+ out << " {\n " ;
133+ out << " \" creator\" : {\n " ;
134+ out << " \" name\" : \" " << creator_name << " \" ,\n " ;
135+ out << " \" version\" : \" " << creator_version << " \" ,\n " ;
136+ out << " \" pid\" : " << creator_pid << " \n " ;
137+ out << " },\n " ;
138+ out << " \" instrument\" : {\n " ;
139+ out << " \" type\" : \" " << instrument_type << " \"\n " ;
140+ out << " },\n " ;
141+ out << " \" benchmarks\" : [\n " ;
142+
143+ out << serialize_benchmark_objects (benchmarks);
144+
145+ out << " \n ]\n }" ;
146+ }
147+
148+ static void append_codspeed_benchmarks_to_json (
149+ const std::vector<CodspeedWalltimeBenchmark> &benchmarks) {
150+
151+ if (benchmarks.empty ()) return ;
152+
153+ std::filesystem::path file_path = get_results_path ();
154+
155+ // Condition: If file is missing or entirely empty, generate fresh
156+ if (!std::filesystem::exists (file_path) || std::filesystem::file_size (file_path) == 0 ) {
157+ write_codspeed_benchmarks_to_json (benchmarks);
158+ return ;
159+ }
160+
161+ // Open in read/write binary mode to prevent Windows newline conversion from messing with stream offsets
162+ std::fstream file (file_path, std::ios::in | std::ios::out | std::ios::binary);
163+ if (!file) {
164+ std::cerr << " Failed to open file for appending: " << file_path << std::endl;
165+ return ;
166+ }
167+
168+ // Back up from the EOF until we strip away '}', ']', and whitespace
169+ file.seekp (0 , std::ios::end);
170+ std::streampos pos = file.tellp ();
171+
172+ int brackets_found = 0 ;
173+ bool array_was_empty = false ;
174+ std::streampos truncation_pos = 0 ;
175+
176+ // Read backwards to safely find where the trailing array structural markers close
177+ while (pos > 0 ) {
178+ pos -= 1 ;
179+ file.seekg (pos);
180+ char ch;
181+ file.get (ch);
182+
183+ if (std::isspace (static_cast <unsigned char >(ch))) {
184+ continue ;
185+ }
186+
187+ if (brackets_found == 0 && ch == ' }' ) {
188+ brackets_found = 1 ;
189+ continue ;
190+ }
191+
192+ if (brackets_found == 1 && ch == ' ]' ) {
193+ brackets_found = 2 ;
194+ continue ;
139195 }
196+
197+ if (brackets_found == 2 ) {
198+ // Check if the last character before whitespace is the open bracket '[' (meaning array was empty)
199+ if (ch == ' [' ) {
200+ array_was_empty = true ;
201+ }
202+ truncation_pos = pos + std::streamoff (1 );
203+ break ;
204+ }
205+ }
206+
207+ if (brackets_found < 2 ) {
208+ std::cerr << " Failed to find valid JSON structural endings to safely append." << std::endl;
209+ return ;
140210 }
141211
142- // Create the file path
143- std::ostringstream file_path;
144- file_path << results_path.string () << " /" << creator_pid << " .json" ;
212+ file.seekp (truncation_pos);
145213
146- // Write to file
147- std::ofstream out_file (file_path.str ());
148- if (out_file.is_open ()) {
149- out_file << oss.str ();
150- out_file.close ();
151- std::cout << " JSON written to " << file_path.str () << std::endl;
214+ if (!array_was_empty) {
215+ file << " ,\n " ; // Commas are mandatory between pre-existing elements
152216 } else {
153- std::cerr << " Unable to open file " << file_path. str () << std::endl ;
217+ file << " \n " ;
154218 }
219+
220+ // Inject the new items
221+ file << serialize_benchmark_objects (benchmarks);
222+
223+ // Re-close the JSON root safely
224+ file << " \n ]\n }" ;
225+
226+ std::filesystem::resize_file (file_path, file.tellp ());
155227}
156228
157229BenchmarkStats compute_benchmark_stats (
@@ -245,7 +317,7 @@ void generate_codspeed_walltime_report(
245317 codspeed_walltime_benchmarks.push_back (codspeed_benchmark);
246318 }
247319
248- write_codspeed_benchmarks_to_json (codspeed_walltime_benchmarks);
320+ append_codspeed_benchmarks_to_json (codspeed_walltime_benchmarks);
249321}
250322
251323} // namespace codspeed
0 commit comments