af710487 |
1 | /////////////////////////////////////////////////////////////////////////////// |
2 | // Copyright (c) 2000-2015 Ericsson Telecom AB |
3 | // All rights reserved. This program and the accompanying materials |
4 | // are made available under the terms of the Eclipse Public License v1.0 |
5 | // which accompanies this distribution, and is available at |
6 | // http://www.eclipse.org/legal/epl-v10.html |
7 | /////////////////////////////////////////////////////////////////////////////// |
8 | |
9 | #include "Profiler.hh" |
10 | #include <sys/time.h> |
11 | #include <stdio.h> |
12 | #include <stdlib.h> |
13 | #include <string.h> |
14 | #include "JSON_Tokenizer.hh" |
15 | #include "memory.h" |
16 | #include "Runtime.hh" |
17 | |
18 | //////////////////////////////////// |
19 | ////////// TTCN3_Profiler ////////// |
20 | //////////////////////////////////// |
21 | |
22 | TTCN3_Profiler ttcn3_prof; |
23 | |
24 | TTCN3_Profiler::TTCN3_Profiler() |
25 | : disable_profiler(FALSE), disable_coverage(FALSE), aggregate_data(FALSE) |
26 | , disable_stats(FALSE) |
27 | { |
28 | database_filename = mcopystr("profiler.db"); |
29 | stats_filename = mcopystr("profiler.stats"); |
30 | reset(); |
31 | } |
32 | |
33 | TTCN3_Profiler::~TTCN3_Profiler() |
34 | { |
35 | if (!disable_profiler || !disable_coverage) { |
36 | import_data(); |
37 | export_data(); |
38 | if (!disable_stats && (TTCN_Runtime::is_single() || TTCN_Runtime::is_hc())) { |
39 | print_stats(); |
40 | } |
41 | } |
42 | for (size_t i = 0; i < profiler_db.size(); ++i) { |
43 | Free(profiler_db[i].filename); |
44 | for (size_t j = 0; j < profiler_db[i].functions.size(); ++j) { |
45 | Free(profiler_db[i].functions[j].name); |
46 | } |
47 | } |
48 | Free(database_filename); |
49 | Free(stats_filename); |
50 | } |
51 | |
52 | void TTCN3_Profiler::set_disable_profiler(boolean p_disable_profiler) |
53 | { |
54 | disable_profiler = p_disable_profiler; |
55 | } |
56 | |
57 | void TTCN3_Profiler::set_disable_coverage(boolean p_disable_coverage) |
58 | { |
59 | disable_coverage = p_disable_coverage; |
60 | } |
61 | |
62 | void TTCN3_Profiler::set_database_filename(const char* p_database_filename) |
63 | { |
64 | Free(database_filename); |
65 | database_filename = mcopystr(p_database_filename); |
66 | } |
67 | |
68 | void TTCN3_Profiler::set_aggregate_data(boolean p_aggregate_data) |
69 | { |
70 | aggregate_data = p_aggregate_data; |
71 | } |
72 | |
73 | void TTCN3_Profiler::set_stats_filename(const char* p_stats_filename) |
74 | { |
75 | Free(stats_filename); |
76 | stats_filename = mcopystr(p_stats_filename); |
77 | } |
78 | |
79 | void TTCN3_Profiler::set_disable_stats(boolean p_disable_stats) |
80 | { |
81 | disable_stats = p_disable_stats; |
82 | } |
83 | |
84 | boolean TTCN3_Profiler::is_profiler_disabled() const |
85 | { |
86 | return disable_profiler; |
87 | } |
88 | |
89 | #define IMPORT_FORMAT_ERROR(cond) \ |
90 | if (cond) { \ |
91 | TTCN_warning("Database format is invalid. Profiler and/or code coverage data will not be loaded."); \ |
92 | return; \ |
93 | } |
94 | |
95 | void TTCN3_Profiler::init_data_file() |
96 | { |
97 | // delete the database file (from the previous run) if data aggregation is not set |
98 | if (!aggregate_data && (!disable_profiler || !disable_coverage)) { |
99 | remove(database_filename); |
100 | } |
101 | } |
102 | |
103 | void TTCN3_Profiler::import_data() |
104 | { |
105 | // open the file, if it exists |
106 | FILE* file = fopen(database_filename, "r"); |
107 | if (NULL == file) { |
108 | return; |
109 | } |
110 | |
111 | // get the file size |
112 | fseek(file, 0, SEEK_END); |
113 | int file_size = ftell(file); |
114 | rewind(file); |
115 | |
116 | // read the entire file into a character buffer |
117 | char* buffer = (char*)Malloc(file_size); |
118 | fread(buffer, 1, file_size, file); |
119 | |
120 | // initialize a JSON tokenizer with the buffer |
121 | JSON_Tokenizer json(buffer, file_size); |
122 | Free(buffer); |
123 | |
124 | // attempt to read tokens from the buffer |
125 | // if the format is invalid, abort the importing process |
126 | json_token_t token = JSON_TOKEN_NONE; |
127 | char* value = NULL; |
128 | size_t value_len = 0; |
129 | |
130 | // start of main array |
131 | json.get_next_token(&token, NULL, NULL); |
132 | IMPORT_FORMAT_ERROR(JSON_TOKEN_ARRAY_START != token); |
133 | |
134 | // read objects (one for each TTCN-3 file), until the main array end mark is reached |
135 | json.get_next_token(&token, NULL, NULL); |
136 | while (JSON_TOKEN_OBJECT_START == token) { |
137 | size_t file_index = 0; |
138 | |
139 | // file name: |
140 | json.get_next_token(&token, &value, &value_len); |
141 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 4 || |
142 | 0 != strncmp(value, "file", value_len)); |
143 | |
144 | // read the file name and see if its record already exists |
145 | json.get_next_token(&token, &value, &value_len); |
146 | IMPORT_FORMAT_ERROR(JSON_TOKEN_STRING != token); |
147 | for (file_index = 0; file_index < profiler_db.size(); ++file_index) { |
148 | if (strlen(profiler_db[file_index].filename) == value_len - 2 && |
149 | 0 == strncmp(profiler_db[file_index].filename, value + 1, value_len - 2)) { |
150 | break; |
151 | } |
152 | } |
153 | |
154 | // insert a new element if the file was not found |
155 | if (profiler_db.size() == file_index) { |
156 | profiler_db_item_t item; |
157 | item.filename = mcopystrn(value + 1, value_len - 2); |
158 | profiler_db.push_back(item); |
159 | } |
160 | |
161 | // functions: |
162 | json.get_next_token(&token, &value, &value_len); |
163 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 9 || |
164 | 0 != strncmp(value, "functions", value_len)); |
165 | |
166 | // read and store the functions (an array of objects, same as before) |
167 | json.get_next_token(&token, NULL, NULL); |
168 | IMPORT_FORMAT_ERROR(JSON_TOKEN_ARRAY_START != token); |
169 | json.get_next_token(&token, NULL, NULL); |
170 | while (JSON_TOKEN_OBJECT_START == token) { |
171 | size_t function_index = 0; |
172 | |
173 | // function name: |
174 | json.get_next_token(&token, &value, &value_len); |
175 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 4 || |
176 | 0 != strncmp(value, "name", value_len)); |
177 | |
178 | // read the function name, it will be checked later |
179 | json.get_next_token(&token, &value, &value_len); |
180 | IMPORT_FORMAT_ERROR(JSON_TOKEN_STRING != token); |
181 | char* function_name = mcopystrn(value + 1, value_len - 2); |
182 | |
183 | // function start line: |
184 | json.get_next_token(&token, &value, &value_len); |
185 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 10 || |
186 | 0 != strncmp(value, "start line", value_len)); |
187 | |
188 | // read the start line and check if the function already exists |
189 | json.get_next_token(&token, &value, &value_len); |
190 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NUMBER != token); |
191 | int start_line = atoi(value); |
192 | for (function_index = 0; function_index < profiler_db[file_index].functions.size(); ++function_index) { |
193 | if (profiler_db[file_index].functions[function_index].lineno == start_line && |
194 | 0 == strcmp(profiler_db[file_index].functions[function_index].name, function_name)) { |
195 | break; |
196 | } |
197 | } |
198 | |
199 | // insert a new element if the function was not found |
200 | if (profiler_db[file_index].functions.size() == function_index) { |
201 | profiler_db_item_t::profiler_function_data_t func_data; |
202 | func_data.name = function_name; |
203 | func_data.lineno = start_line; |
204 | func_data.exec_count = 0; |
205 | func_data.total_time = 0.0; |
206 | profiler_db[file_index].functions.push_back(func_data); |
207 | } |
208 | |
209 | if (!disable_coverage) { |
210 | // function execution count: |
211 | json.get_next_token(&token, &value, &value_len); |
212 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 15 || |
213 | 0 != strncmp(value, "execution count", value_len)); |
214 | |
215 | // read the execution count and add it to the current data |
216 | json.get_next_token(&token, &value, &value_len); |
217 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NUMBER != token); |
218 | profiler_db[file_index].functions[function_index].exec_count += atoi(value); |
219 | } |
220 | |
221 | if (!disable_profiler) { |
222 | // total function execution time: |
223 | json.get_next_token(&token, &value, &value_len); |
224 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 10 || |
225 | 0 != strncmp(value, "total time", value_len)); |
226 | |
227 | // read the total time and add it to the current data |
228 | json.get_next_token(&token, &value, &value_len); |
229 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NUMBER != token); |
230 | profiler_db[file_index].functions[function_index].total_time += atof(value); |
231 | } |
232 | |
233 | // end of the function's object |
234 | json.get_next_token(&token, NULL, NULL); |
235 | IMPORT_FORMAT_ERROR(JSON_TOKEN_OBJECT_END != token); |
236 | |
237 | // read the next token (either the start of another object or the function array end) |
238 | json.get_next_token(&token, NULL, NULL); |
239 | } |
240 | |
241 | // function array end |
242 | IMPORT_FORMAT_ERROR(JSON_TOKEN_ARRAY_END != token); |
243 | |
244 | // lines: |
245 | json.get_next_token(&token, &value, &value_len); |
246 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 5 || |
247 | 0 != strncmp(value, "lines", value_len)); |
248 | |
249 | // read and store the lines (an array of objects, same as before) |
250 | json.get_next_token(&token, NULL, NULL); |
251 | IMPORT_FORMAT_ERROR(JSON_TOKEN_ARRAY_START != token); |
252 | json.get_next_token(&token, NULL, NULL); |
253 | while (JSON_TOKEN_OBJECT_START == token) { |
254 | |
255 | // line number: |
256 | json.get_next_token(&token, &value, &value_len); |
257 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 6 || |
258 | 0 != strncmp(value, "number", value_len)); |
259 | |
260 | // read the line number and check if the line already exists |
261 | json.get_next_token(&token, &value, &value_len); |
262 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NUMBER != token); |
263 | int lineno = atoi(value); |
264 | IMPORT_FORMAT_ERROR(lineno < 0); |
265 | size_t line_no = lineno; |
266 | if (line_no >= profiler_db[file_index].lines.size()) { |
267 | for (size_t i = profiler_db[file_index].lines.size(); i <= line_no; ++i) { |
268 | profiler_db_item_t::profiler_line_data_t line_data; |
269 | line_data.total_time = 0.0; |
270 | line_data.exec_count = 0; |
271 | profiler_db[file_index].lines.push_back(line_data); |
272 | } |
273 | } |
274 | |
275 | if (!disable_coverage) { |
276 | // line execution count: |
277 | json.get_next_token(&token, &value, &value_len); |
278 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 15 || |
279 | 0 != strncmp(value, "execution count", value_len)); |
280 | |
281 | // read the execution count and add it to the current data |
282 | json.get_next_token(&token, &value, &value_len); |
283 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NUMBER != token); |
284 | profiler_db[file_index].lines[line_no].exec_count += atoi(value); |
285 | } |
286 | |
287 | if (!disable_profiler) { |
288 | // total line execution time: |
289 | json.get_next_token(&token, &value, &value_len); |
290 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NAME != token || value_len != 10 || |
291 | 0 != strncmp(value, "total time", value_len)); |
292 | |
293 | // read the total time and add it to the current data |
294 | json.get_next_token(&token, &value, &value_len); |
295 | IMPORT_FORMAT_ERROR(JSON_TOKEN_NUMBER != token); |
296 | profiler_db[file_index].lines[line_no].total_time += atof(value); |
297 | } |
298 | |
299 | // end of the line's object |
300 | json.get_next_token(&token, NULL, NULL); |
301 | IMPORT_FORMAT_ERROR(JSON_TOKEN_OBJECT_END != token); |
302 | |
303 | // read the next token (either the start of another object or the line array end) |
304 | json.get_next_token(&token, NULL, NULL); |
305 | } |
306 | |
307 | // line array end |
308 | IMPORT_FORMAT_ERROR(JSON_TOKEN_ARRAY_END != token); |
309 | |
310 | // end of the file's object |
311 | json.get_next_token(&token, NULL, NULL); |
312 | IMPORT_FORMAT_ERROR(JSON_TOKEN_OBJECT_END != token); |
313 | |
314 | // read the next token (either the start of another object or the main array end) |
315 | json.get_next_token(&token, NULL, NULL); |
316 | } |
317 | |
318 | // main array end |
319 | IMPORT_FORMAT_ERROR(JSON_TOKEN_ARRAY_END != token); |
320 | } |
321 | |
322 | void TTCN3_Profiler::export_data() |
323 | { |
324 | // nothing to export if the database is empty |
325 | if (profiler_db.empty()) { |
326 | return; |
327 | } |
328 | |
329 | // check whether the file can be opened for writing |
330 | FILE* file = fopen(database_filename, "w"); |
331 | if (NULL == file) { |
332 | TTCN_warning("Could not open file '%s' for writing. Profiler and/or code coverage " |
333 | "data will not be saved.", database_filename); |
334 | return; |
335 | } |
336 | |
337 | // use the JSON tokenizer to create a JSON document from the database |
338 | JSON_Tokenizer json(true); |
339 | |
340 | // main array, contains an element for each file |
341 | json.put_next_token(JSON_TOKEN_ARRAY_START, NULL); |
342 | for (size_t i = 0; i < profiler_db.size(); ++i) { |
343 | |
344 | // each file's data is stored in an object |
345 | json.put_next_token(JSON_TOKEN_OBJECT_START, NULL); |
346 | |
347 | // store the file name |
348 | json.put_next_token(JSON_TOKEN_NAME, "file"); |
349 | char* file_name_str = mprintf("\"%s\"", profiler_db[i].filename); |
350 | json.put_next_token(JSON_TOKEN_STRING, file_name_str); |
351 | Free(file_name_str); |
352 | |
353 | // store the function data in an array (one element for each function) |
354 | json.put_next_token(JSON_TOKEN_NAME, "functions"); |
355 | json.put_next_token(JSON_TOKEN_ARRAY_START, NULL); |
356 | for (size_t j = 0; j < profiler_db[i].functions.size(); ++j) { |
357 | |
358 | // the data is stored in an object for each function |
359 | json.put_next_token(JSON_TOKEN_OBJECT_START, NULL); |
360 | |
361 | // store the function name |
362 | json.put_next_token(JSON_TOKEN_NAME, "name"); |
363 | char* func_name_str = mprintf("\"%s\"", profiler_db[i].functions[j].name); |
364 | json.put_next_token(JSON_TOKEN_STRING, func_name_str); |
365 | Free(func_name_str); |
366 | |
367 | // store the function start line |
368 | json.put_next_token(JSON_TOKEN_NAME, "start line"); |
369 | char* start_line_str = mprintf("%d", profiler_db[i].functions[j].lineno); |
370 | json.put_next_token(JSON_TOKEN_NUMBER, start_line_str); |
371 | Free(start_line_str); |
372 | |
373 | if (!disable_coverage) { |
374 | // store the function execution count |
375 | json.put_next_token(JSON_TOKEN_NAME, "execution count"); |
376 | char* exec_count_str = mprintf("%d", profiler_db[i].functions[j].exec_count); |
377 | json.put_next_token(JSON_TOKEN_NUMBER, exec_count_str); |
378 | Free(exec_count_str); |
379 | } |
380 | |
381 | if (!disable_profiler) { |
382 | // store the function's total execution time |
383 | json.put_next_token(JSON_TOKEN_NAME, "total time"); |
384 | char* exec_count_str = mprintf("%.6lf", profiler_db[i].functions[j].total_time); |
385 | json.put_next_token(JSON_TOKEN_NUMBER, exec_count_str); |
386 | Free(exec_count_str); |
387 | } |
388 | |
389 | // end of function object |
390 | json.put_next_token(JSON_TOKEN_OBJECT_END, NULL); |
391 | } |
392 | |
393 | // end of function data array |
394 | json.put_next_token(JSON_TOKEN_ARRAY_END, NULL); |
395 | |
396 | // store the line data in an array (one element for each line with useful data) |
397 | json.put_next_token(JSON_TOKEN_NAME, "lines"); |
398 | json.put_next_token(JSON_TOKEN_ARRAY_START, NULL); |
399 | for (size_t j = 0; j < profiler_db[i].lines.size(); ++j) { |
400 | if (0.0 != profiler_db[i].lines[j].total_time || |
401 | 0 != profiler_db[i].lines[j].exec_count) { |
402 | |
403 | // store line data in an object |
404 | json.put_next_token(JSON_TOKEN_OBJECT_START, NULL); |
405 | |
406 | // store the line number |
407 | json.put_next_token(JSON_TOKEN_NAME, "number"); |
408 | char* line_number_str = mprintf("%lu", j); |
409 | json.put_next_token(JSON_TOKEN_NUMBER, line_number_str); |
410 | Free(line_number_str); |
411 | |
412 | if (!disable_coverage) { |
413 | // store the line execution count |
414 | json.put_next_token(JSON_TOKEN_NAME, "execution count"); |
415 | char* exec_count_str = mprintf("%d", profiler_db[i].lines[j].exec_count); |
416 | json.put_next_token(JSON_TOKEN_NUMBER, exec_count_str); |
417 | Free(exec_count_str); |
418 | } |
419 | |
420 | if (!disable_profiler) { |
421 | // store the line's total execution time |
422 | json.put_next_token(JSON_TOKEN_NAME, "total time"); |
423 | char* exec_count_str = mprintf("%.6lf", profiler_db[i].lines[j].total_time); |
424 | json.put_next_token(JSON_TOKEN_NUMBER, exec_count_str); |
425 | Free(exec_count_str); |
426 | } |
427 | |
428 | // end of this line's object |
429 | json.put_next_token(JSON_TOKEN_OBJECT_END, NULL); |
430 | } |
431 | } |
432 | |
433 | // end of line data array |
434 | json.put_next_token(JSON_TOKEN_ARRAY_END, NULL); |
435 | |
436 | // end of this file's object |
437 | json.put_next_token(JSON_TOKEN_OBJECT_END, NULL); |
438 | } |
439 | |
440 | // end of main array |
441 | json.put_next_token(JSON_TOKEN_ARRAY_END, NULL); |
442 | |
443 | // write the JSON document into the file |
444 | fprintf(file, "%s\n", json.get_buffer()); |
445 | fclose(file); |
446 | } |
447 | |
448 | void TTCN3_Profiler::print_stats() |
449 | { |
450 | if (profiler_db.empty()) { |
451 | return; |
452 | } |
453 | |
454 | // title |
455 | char* title_str = mprintf( |
456 | "##################################################\n" |
457 | "%s## TTCN-3 %s%s%sstatistics ##%s\n" |
458 | "##################################################\n\n\n" |
459 | , disable_profiler ? "#######" : (disable_coverage ? "#########" : "") |
460 | , disable_profiler ? "" : "profiler " |
461 | , (disable_profiler || disable_coverage) ? "" : "and " |
462 | , disable_coverage ? "" : "code coverage " |
463 | , disable_profiler ? "######" : (disable_coverage ? "#########" : "")); |
464 | |
465 | // line data |
466 | char* line_data_str = mprintf( |
467 | "-------------------------------------------------\n" |
468 | "%s- Code line data (%s%s%s) -%s\n" |
469 | "-------------------------------------------------\n" |
470 | , disable_profiler ? "-------" : (disable_coverage ? "---------" : "") |
471 | , disable_profiler ? "" : "total time" |
472 | , (disable_profiler || disable_coverage) ? "" : " / " |
473 | , disable_coverage ? "" : "execution count" |
474 | , disable_profiler ? "------" : (disable_coverage ? "---------" : "")); |
475 | |
476 | // average time / exec count for lines |
477 | char* line_avg_str = NULL; |
478 | if (!disable_coverage && !disable_profiler) { |
479 | line_avg_str = mcopystr( |
480 | "-------------------------------------------------\n" |
481 | "- Average time / execution count for code lines -\n" |
482 | "-------------------------------------------------\n"); |
483 | } |
484 | |
485 | // function data |
486 | char* func_data_str = mprintf( |
487 | "------------------------------------------------\n" |
488 | "%s- Function data (%s%s%s) -%s\n" |
489 | "------------------------------------------------\n" |
490 | , disable_profiler ? "-------" : (disable_coverage ? "---------" : "") |
491 | , disable_profiler ? "" : "total time" |
492 | , (disable_profiler || disable_coverage) ? "" : " / " |
493 | , disable_coverage ? "" : "execution count" |
494 | , disable_profiler ? "------" : (disable_coverage ? "---------" : "")); |
495 | |
496 | // average time / exec count for functions |
497 | char* func_avg_str = NULL; |
498 | if (!disable_coverage && !disable_profiler) { |
499 | func_avg_str = mcopystr( |
500 | "------------------------------------------------\n" |
501 | "- Average time / execution count for functions -\n" |
502 | "------------------------------------------------\n"); |
503 | } |
504 | |
505 | // cycle through the database and gather the necessary data |
506 | for (size_t i = 0; i < profiler_db.size(); ++i) { |
507 | if (i > 0) { |
508 | // add separators between files |
509 | line_data_str = mputstr(line_data_str, "-------------------------------------------------\n"); |
510 | func_data_str = mputstr(func_data_str, "------------------------------------------------\n"); |
511 | if (!disable_profiler && !disable_coverage) { |
512 | line_avg_str = mputstr(line_avg_str, "-------------------------------------------------\n"); |
513 | func_avg_str = mputstr(func_avg_str, "------------------------------------------------\n"); |
514 | } |
515 | } |
516 | |
517 | // lines |
518 | for (size_t j = 0; j < profiler_db[i].lines.size(); ++j) { |
519 | if (0.0 != profiler_db[i].lines[j].total_time || |
520 | 0 != profiler_db[i].lines[j].exec_count) { |
521 | if (!disable_profiler) { |
522 | line_data_str = mputprintf(line_data_str, "%.6lfs", profiler_db[i].lines[j].total_time); |
523 | if (!disable_coverage) { |
524 | line_data_str = mputstrn(line_data_str, "\t/\t", 3); |
525 | line_avg_str = mputprintf(line_avg_str, "%.6lfs", |
526 | profiler_db[i].lines[j].total_time / profiler_db[i].lines[j].exec_count); |
527 | } |
528 | } |
529 | if (!disable_coverage) { |
530 | line_data_str = mputprintf(line_data_str, "%d", profiler_db[i].lines[j].exec_count); |
531 | } |
532 | |
533 | // line specification (including function name for the function's start line) |
534 | char* line_spec_str = mprintf("\t%s:%lu", profiler_db[i].filename, j); |
535 | int func = get_function(i, j); |
536 | if (-1 != func) { |
537 | line_spec_str = mputprintf(line_spec_str, " [%s]", profiler_db[i].functions[func].name); |
538 | } |
539 | line_spec_str = mputstrn(line_spec_str, "\n", 1); |
540 | |
541 | // add the line spec string to the other strings |
542 | line_data_str = mputstr(line_data_str, line_spec_str); |
543 | if (!disable_profiler && !disable_coverage) { |
544 | line_avg_str = mputstr(line_avg_str, line_spec_str); |
545 | } |
546 | } |
547 | } |
548 | |
549 | // functions |
550 | for (size_t j = 0; j < profiler_db[i].functions.size(); ++j) { |
551 | if (!disable_profiler) { |
552 | func_data_str = mputprintf(func_data_str, "%.6lfs", profiler_db[i].functions[j].total_time); |
553 | if (!disable_coverage) { |
554 | func_data_str = mputstrn(func_data_str, "\t/\t", 3); |
555 | func_avg_str = mputprintf(func_avg_str, "%.6lfs", |
556 | profiler_db[i].functions[j].total_time / profiler_db[i].functions[j].exec_count); |
557 | } |
558 | } |
559 | if (!disable_coverage) { |
560 | func_data_str = mputprintf(func_data_str, "%d", profiler_db[i].functions[j].exec_count); |
561 | } |
562 | |
563 | // functions specification |
564 | char* func_spec_str = mprintf("\t%s:%d [%s]\n", profiler_db[i].filename, |
565 | profiler_db[i].functions[j].lineno, profiler_db[i].functions[j].name); |
566 | |
567 | // add the line spec string to the other strings |
568 | func_data_str = mputstr(func_data_str, func_spec_str); |
569 | if (!disable_profiler && !disable_coverage) { |
570 | func_avg_str = mputstr(func_avg_str, func_spec_str); |
571 | } |
572 | } |
573 | } |
574 | |
575 | // add new lines at the end of each segment |
576 | line_data_str = mputstrn(line_data_str, "\n", 1); |
577 | func_data_str = mputstrn(func_data_str, "\n", 1); |
578 | if (!disable_profiler && !disable_coverage) { |
579 | line_avg_str = mputstrn(line_avg_str, "\n", 1); |
580 | func_avg_str = mputstrn(func_avg_str, "\n", 1); |
581 | } |
582 | |
583 | // write the statistics to the specified file |
584 | FILE* file = fopen(stats_filename, "w"); |
585 | if (NULL == file) { |
586 | TTCN_warning("Could not open file '%s' for writing. Profiler and/or code coverage " |
587 | "statistics will not be saved.", stats_filename); |
588 | return; |
589 | } |
590 | fprintf(file, "%s%s%s%s%s" |
591 | , title_str, line_data_str |
592 | , (disable_profiler || disable_coverage) ? "" : line_avg_str |
593 | , func_data_str, (disable_profiler || disable_coverage) ? "" : func_avg_str); |
594 | fclose(file); |
595 | } |
596 | |
597 | void TTCN3_Profiler::reset() |
598 | { |
599 | prev_time = 0.0; |
600 | prev_file = NULL; |
601 | prev_line = -1; |
602 | prev_stack_len = 0; |
603 | } |
604 | |
605 | double TTCN3_Profiler::get_time() |
606 | { |
607 | timeval tv; |
608 | gettimeofday(&tv, NULL); |
609 | return tv.tv_sec + tv.tv_usec / 1000000.0; |
610 | } |
611 | |
612 | void TTCN3_Profiler::enter_function(const char* filename, int lineno, const char* function_name) |
613 | { |
614 | if (disable_profiler && disable_coverage) { |
615 | return; |
616 | } |
617 | |
618 | // Note that the execution time of the last line in a function |
619 | // is measured by using the stack depth. |
620 | execute_line(filename, lineno); |
621 | |
622 | int element = get_element(filename); |
623 | |
624 | // store function data |
625 | int func = get_function(element, lineno); |
626 | if (-1 == func) { |
627 | create_function(element, lineno, function_name); |
628 | func = profiler_db[element].functions.size() - 1; |
629 | } |
630 | |
631 | if (!disable_coverage) { |
632 | ++profiler_db[element].functions[func].exec_count; |
633 | } |
634 | } |
635 | |
636 | void TTCN3_Profiler::execute_line(const char* filename, int lineno) |
637 | { |
638 | if (disable_profiler && disable_coverage) { |
639 | return; |
640 | } |
641 | |
642 | if (!disable_profiler) { |
643 | double currentTime = get_time(); |
644 | |
645 | // prev line should not be measured, because it is still running: we are in longer stack level |
646 | if (0.0 == prev_time || TTCN3_Stack_Depth::depth() > prev_stack_len) { |
647 | // add prev timer to call stack: |
648 | TTCN3_Stack_Depth::add_stack(prev_stack_len, prev_file, filename, prev_line, lineno); |
649 | } |
650 | else { |
651 | // if stack level is the same or higher: current line should be measured: |
652 | double elapsed = currentTime - prev_time; |
653 | |
654 | // add the elapsed time to the time of the previous line: |
655 | add_line_time(elapsed, get_element(prev_file), prev_line); |
656 | |
657 | TTCN3_Stack_Depth::update_stack_elapsed(elapsed); |
658 | } |
659 | } |
660 | |
661 | // several instructions could be in the same line, only count the line once |
662 | if (!disable_coverage && (lineno != prev_line || NULL == prev_file || |
663 | 0 != strcmp(prev_file, filename))) { |
664 | int element = get_element(filename); |
665 | |
666 | // make sure the line exists in the database |
667 | create_lines(element, lineno); |
668 | |
669 | // increase line execution count |
670 | ++profiler_db[element].lines[lineno].exec_count; |
671 | } |
672 | |
673 | // store the current location as previous for the next call |
674 | set_prev(disable_profiler ? -1 : TTCN3_Stack_Depth::depth(), filename, lineno); |
675 | } |
676 | |
677 | int TTCN3_Profiler::get_element(const char* filename) |
678 | { |
679 | for (size_t i = 0; i < profiler_db.size(); ++i) { |
680 | if (0 == strcmp(profiler_db[i].filename, filename)) { |
681 | return i; |
682 | } |
683 | } |
684 | |
685 | profiler_db_item_t item; |
686 | item.filename = mcopystr(filename); |
687 | profiler_db.push_back(item); |
688 | return profiler_db.size() - 1; |
689 | } |
690 | |
691 | int TTCN3_Profiler::get_function(int element, int lineno) |
692 | { |
693 | for (size_t i = 0; i < profiler_db[element].functions.size(); ++i) { |
694 | if (profiler_db[element].functions[i].lineno == lineno) { |
695 | return i; |
696 | } |
697 | } |
698 | return -1; |
699 | } |
700 | |
701 | void TTCN3_Profiler::create_function(int element, int lineno, const char* function_name) |
702 | { |
703 | profiler_db_item_t::profiler_function_data_t func_data; |
704 | func_data.lineno = lineno; |
705 | func_data.total_time = 0.0; |
706 | func_data.exec_count = 0; |
707 | func_data.name = mcopystr(function_name); |
708 | profiler_db[element].functions.push_back(func_data); |
709 | } |
710 | |
711 | void TTCN3_Profiler::create_lines(int element, int lineno) |
712 | { |
713 | // set 0 for the unknown lines |
714 | size_t line_no = lineno; |
715 | if (profiler_db[element].lines.size() <= line_no) { |
716 | for (size_t i = profiler_db[element].lines.size(); i <= line_no; ++i) { |
717 | profiler_db_item_t::profiler_line_data_t line_data; |
718 | line_data.total_time = 0.0; |
719 | line_data.exec_count = 0; |
720 | profiler_db[element].lines.push_back(line_data); |
721 | } |
722 | } |
723 | } |
724 | |
725 | void TTCN3_Profiler::add_line_time(double elapsed, int element, int lineno) |
726 | { |
727 | if (-1 == lineno) { |
728 | return; |
729 | } |
730 | |
731 | // ensure the line data exists |
732 | create_lines(element, lineno); |
733 | |
734 | // increase the time of the line in the current file: |
735 | profiler_db[element].lines[lineno].total_time += elapsed; |
736 | } |
737 | |
738 | void TTCN3_Profiler::add_function_time(double elapsed, int element, int lineno) |
739 | { |
740 | int func = get_function(element, lineno); |
741 | if (-1 == func) { |
742 | return; |
743 | } |
744 | profiler_db[element].functions[func].total_time += elapsed; |
745 | } |
746 | |
747 | void TTCN3_Profiler::update_last() |
748 | { |
749 | if (0.0 == prev_time) { |
750 | return; |
751 | } |
752 | |
753 | double currentTime = get_time(); |
754 | double elapsed = currentTime - prev_time; |
755 | |
756 | int element = get_element(prev_file); |
757 | |
758 | // add the elapsed time to the time of the previous line: |
759 | add_line_time(elapsed, element, prev_line); |
760 | TTCN3_Stack_Depth::update_stack_elapsed(elapsed); |
761 | |
762 | // reset measurement: |
763 | prev_time = 0.0; |
764 | } |
765 | |
766 | void TTCN3_Profiler::set_prev(int stack_len, const char* filename, int lineno) |
767 | { |
768 | prev_file = filename; |
769 | prev_line = lineno; |
770 | if (!disable_profiler) { |
771 | prev_time = get_time(); |
772 | prev_stack_len = stack_len; |
773 | } |
774 | } |
775 | |
776 | ///////////////////////////////////// |
777 | ///////// TTCN3_Stack_Depth ///////// |
778 | ///////////////////////////////////// |
779 | |
780 | int TTCN3_Stack_Depth::current_depth = -1; |
781 | Vector<TTCN3_Stack_Depth::call_stack_timer_item_t> TTCN3_Stack_Depth::call_stack_timer_db; |
782 | |
783 | TTCN3_Stack_Depth::TTCN3_Stack_Depth() |
784 | { |
785 | if (ttcn3_prof.is_profiler_disabled()) { |
786 | return; |
787 | } |
788 | ++current_depth; |
789 | } |
790 | |
791 | TTCN3_Stack_Depth::~TTCN3_Stack_Depth() |
792 | { |
793 | if (ttcn3_prof.is_profiler_disabled()) { |
794 | return; |
795 | } |
796 | ttcn3_prof.update_last(); |
797 | remove_stack(); |
798 | if (0 == current_depth) { |
799 | ttcn3_prof.reset(); |
800 | } |
801 | --current_depth; |
802 | } |
803 | |
804 | void TTCN3_Stack_Depth::add_stack(int stack_len, const char* caller_file, const char* func_file, |
805 | int caller_line, int start_line) |
806 | { |
807 | call_stack_timer_item_t item; |
808 | item.stack_len = stack_len; |
809 | item.caller_file = caller_file; |
810 | item.func_file = func_file; |
811 | item.caller_line = caller_line; |
812 | item.start_line = start_line; |
813 | item.elapsed = 0.0; |
814 | call_stack_timer_db.push_back(item); |
815 | } |
816 | |
817 | void TTCN3_Stack_Depth::remove_stack() |
818 | { |
819 | // if stack level is the same or higher: measure the time: |
820 | double elapsed = call_stack_timer_db[current_depth].elapsed; |
821 | |
822 | int element = ttcn3_prof.get_element(call_stack_timer_db[current_depth].func_file); |
823 | |
824 | // add elapsed time to the total execution time of the previous line: |
825 | ttcn3_prof.add_function_time(elapsed, element, call_stack_timer_db[current_depth].start_line); |
826 | |
827 | ttcn3_prof.set_prev(call_stack_timer_db[current_depth].stack_len, |
828 | call_stack_timer_db[current_depth].caller_file, |
829 | call_stack_timer_db[current_depth].caller_line); |
830 | |
831 | call_stack_timer_db.erase_at(current_depth); |
832 | } |
833 | |
834 | void TTCN3_Stack_Depth::update_stack_elapsed(double elapsed) |
835 | { |
836 | for(int i = 0; i <= current_depth; i++) { |
837 | call_stack_timer_db[i].elapsed += elapsed; |
838 | } |
839 | } |