56542971ca10a77d41cee7e7d8c2a9d6a0c3745b
[geeqie.git] / src / debug.cc
1 /*
2  * Copyright (C) 2008 - 2016 The Geeqie Team
3  *
4  * Authors: Vladimir Nadvornik, Laurent Monin
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "main.h"
22 #include "debug.h"
23
24 #include "filedata.h"
25 #include "logwindow.h"
26 #include "misc.h"
27 #include "ui-fileops.h"
28
29 #ifdef HAVE_EXECINFO_H
30 #include <execinfo.h>
31 #endif
32
33 #include <regex.h>
34
35 /*
36  * Logging functions
37  */
38 static gchar *regexp = nullptr;
39
40 static gboolean log_msg_cb(gpointer data)
41 {
42         auto buf = static_cast<gchar *>(data);
43         log_window_append(buf, LOG_MSG);
44         g_free(buf);
45         return FALSE;
46 }
47
48 /**
49  * @brief Appends a user information message to the log window queue
50  * @param data The message
51  * @returns FALSE
52  *
53  * If the first word of the message is either "error" or "warning"
54  * (case insensitive) the message is color-coded appropriately
55  */
56 static gboolean log_normal_cb(gpointer data)
57 {
58         auto buf = static_cast<gchar *>(data);
59         gchar *buf_casefold = g_utf8_casefold(buf, -1);
60         gchar *error_casefold = g_utf8_casefold(_("error"), -1);
61         gchar *warning_casefold = g_utf8_casefold(_("warning"), -1);
62
63         if (buf_casefold == g_strstr_len(buf_casefold, -1, error_casefold))
64                 {
65                 log_window_append(buf, LOG_ERROR);
66                 }
67         else if (buf_casefold == g_strstr_len(buf_casefold, -1, warning_casefold))
68                 {
69                 log_window_append(buf, LOG_WARN);
70                 }
71         else
72                 {
73                 log_window_append(buf, LOG_NORMAL);
74                 }
75
76         g_free(buf);
77         g_free(buf_casefold);
78         g_free(error_casefold);
79         g_free(warning_casefold);
80         return FALSE;
81 }
82
83 void log_domain_print_message(const gchar *domain, gchar *buf)
84 {
85         gchar *buf_nl;
86         regex_t regex;
87         gint ret_comp, ret_exec;
88
89         buf_nl = g_strconcat(buf, "\n", NULL);
90
91         if (regexp && command_line)
92                 {
93                         ret_comp = regcomp(&regex, regexp, 0);
94                         if (!ret_comp)
95                                 {
96                                 ret_exec = regexec(&regex, buf_nl, 0, nullptr, 0);
97
98                                 if (!ret_exec)
99                                         {
100                                         print_term(FALSE, buf_nl);
101                                         if (strcmp(domain, DOMAIN_INFO) == 0)
102                                                 g_idle_add(log_normal_cb, buf_nl);
103                                         else
104                                                 g_idle_add(log_msg_cb, buf_nl);
105                                         }
106                                 regfree(&regex);
107                                 }
108                 }
109         else
110                 {
111                 print_term(FALSE, buf_nl);
112                 if (strcmp(domain, DOMAIN_INFO) == 0)
113                         g_idle_add(log_normal_cb, buf_nl);
114                 else
115                         g_idle_add(log_msg_cb, buf_nl);
116                 }
117         g_free(buf);
118 }
119
120 void log_domain_print_debug(const gchar *domain, const gchar *file_name, int line_number, const gchar *function_name, const gchar *format, ...)
121 {
122         va_list ap;
123         gchar *message;
124         gchar *location;
125         gchar *buf;
126
127         va_start(ap, format);
128         message = g_strdup_vprintf(format, ap);
129         va_end(ap);
130
131         if (options && options->log_window.timer_data)
132                 {
133                 location = g_strdup_printf("%s:%s:%d:%s:", get_exec_time(), file_name, line_number, function_name);
134                 }
135         else
136                 {
137                 location = g_strdup_printf("%s:%d:%s:", file_name, line_number, function_name);
138                 }
139
140         buf = g_strconcat(location, message, NULL);
141         log_domain_print_message(domain,buf);
142         g_free(location);
143         g_free(message);
144 }
145
146 void log_domain_printf(const gchar *domain, const gchar *format, ...)
147 {
148         va_list ap;
149         gchar *buf;
150
151         va_start(ap, format);
152         buf = g_strdup_vprintf(format, ap);
153         va_end(ap);
154
155         log_domain_print_message(domain, buf);
156 }
157
158 /*
159  * Debugging only functions
160  */
161
162 #ifdef DEBUG
163
164 static gint debug_level = DEBUG_LEVEL_MIN;
165
166
167 gint get_debug_level()
168 {
169         return debug_level;
170 }
171
172 void set_debug_level(gint new_level)
173 {
174         debug_level = CLAMP(new_level, DEBUG_LEVEL_MIN, DEBUG_LEVEL_MAX);
175 }
176
177 void debug_level_add(gint delta)
178 {
179         set_debug_level(debug_level + delta);
180 }
181
182 gint required_debug_level(gint level)
183 {
184         return (debug_level >= level);
185 }
186
187 static gint timeval_delta(struct timeval *result, struct timeval *x, struct timeval *y)
188 {
189         if (x->tv_usec < y->tv_usec)
190                 {
191                 gint nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
192                 y->tv_usec -= 1000000 * nsec;
193                 y->tv_sec += nsec;
194                 }
195
196         if (x->tv_usec - y->tv_usec > 1000000)
197                 {
198                 gint nsec = (x->tv_usec - y->tv_usec) / 1000000;
199                 y->tv_usec += 1000000 * nsec;
200                 y->tv_sec -= nsec;
201         }
202
203         result->tv_sec = x->tv_sec - y->tv_sec;
204         result->tv_usec = x->tv_usec - y->tv_usec;
205
206         return x->tv_sec < y->tv_sec;
207 }
208
209 const gchar *get_exec_time()
210 {
211         static gchar timestr[30];
212         static struct timeval start_tv = {0, 0};
213         static struct timeval previous = {0, 0};
214         static gint started = 0;
215
216         struct timeval tv = {0, 0};
217         static struct timeval delta = {0, 0};
218
219         gettimeofday(&tv, nullptr);
220
221         if (start_tv.tv_sec == 0) start_tv = tv;
222
223         tv.tv_sec -= start_tv.tv_sec;
224         if (tv.tv_usec >= start_tv.tv_usec)
225                 tv.tv_usec -= start_tv.tv_usec;
226         else
227                 {
228                 tv.tv_usec += 1000000 - start_tv.tv_usec;
229                 tv.tv_sec -= 1;
230                 }
231
232         if (started) timeval_delta(&delta, &tv, &previous);
233
234         previous = tv;
235         started = 1;
236
237         g_snprintf(timestr, sizeof(timestr), "%5d.%06d (+%05d.%06d)", static_cast<gint>(tv.tv_sec), static_cast<gint>(tv.tv_usec), static_cast<gint>(delta.tv_sec), static_cast<gint>(delta.tv_usec));
238
239         return timestr;
240 }
241
242 void init_exec_time()
243 {
244         get_exec_time();
245 }
246
247 void set_regexp(const gchar *cmd_regexp)
248 {
249         regexp = g_strdup(cmd_regexp);
250 }
251
252 gchar *get_regexp()
253 {
254         return g_strdup(regexp);
255 }
256
257 #ifdef HAVE_EXECINFO_H
258 /**
259  * @brief Backtrace of geeqie files
260  * @param file
261  * @param function
262  * @param line
263  *
264  * Requires command line program addr2line \n
265  * Prints the contents of the backtrace buffer for Geeqie files. \n
266  * Format printed is: \n
267  * <full path to source file>:<line number>
268  *
269  * The log window F1 command and Edit/Preferences/Behavior/Log Window F1
270  * Command may be used to open an editor at a backtrace location.
271  */
272 void log_print_backtrace(const gchar *file, gint line, const gchar *function)
273 {
274         FILE *fp;
275         char **bt_syms;
276         char path[2048];
277         gchar *address_offset;
278         gchar *cmd_line;
279         gchar *exe_path;
280         gchar *function_name = nullptr;
281         gchar *paren_end;
282         gchar *paren_start;
283         gint bt_size;
284         gint i;
285         void *bt[1024];
286
287         if (runcmd(reinterpret_cast<const gchar *>("which addr2line >/dev/null 2>&1")) == 0)
288                 {
289                 exe_path = g_path_get_dirname(gq_executable_path);
290                 bt_size = backtrace(bt, 1024);
291                 bt_syms = backtrace_symbols(bt, bt_size);
292
293                 log_printf("Backtrace start");
294                 log_printf("%s/../%s:%d %s\n", exe_path, file, line, function);
295
296                 /* Last item is always "??:?", so ignore it */
297                 for (i = 1; i < bt_size - 1; i++)
298                         {
299                         if (strstr(bt_syms[i], GQ_APPNAME_LC))
300                                 {
301                                 paren_start = g_strstr_len(bt_syms[i], -1, "(");
302                                 paren_end = g_strstr_len(bt_syms[i], -1, ")");
303                                 address_offset = g_strndup(paren_start + 1, paren_end - paren_start - 1);
304
305                                 cmd_line = g_strconcat("addr2line -p -f -C -e ", gq_executable_path, " ", address_offset, NULL);
306
307                                 fp = popen(cmd_line, "r");
308                                 if (fp == nullptr)
309                                         {
310                                         log_printf("Failed to run command: %s", cmd_line);
311                                         }
312                                 else
313                                         {
314                                         while (fgets(path, sizeof(path), fp) != nullptr)
315                                                 {
316                                                 /* Remove redundant newline */
317                                                 path[strlen(path) - 1] = '\0';
318
319                                                 if (g_strstr_len(path, strlen(path), "(") != nullptr)
320                                                         {
321                                                         function_name = g_strndup(path, g_strstr_len(path, strlen(path), "(") - path);
322                                                         }
323                                                 else
324                                                         {
325                                                         function_name = g_strdup("");
326                                                         }
327                                                 log_printf("%s %s", g_strstr_len(path, -1, "at ") + 3, function_name);
328
329                                                 g_free(function_name);
330                                                 }
331                                         }
332
333                                 pclose(fp);
334
335                                 g_free(address_offset);
336                                 g_free(cmd_line);
337                                 }
338                         }
339                 log_printf("Backtrace end");
340
341                 free(bt_syms);
342                 g_free(exe_path);
343                 }
344 }
345 #else
346 void log_print_backtrace(const gchar *, gint, const gchar *)
347 {
348 }
349 #endif
350
351 /**
352  * @brief Print ref. count and image name
353  * @param file
354  * @param function
355  * @param line
356  *
357  * Print image ref. count and full path name of all images in
358  * the file_data_pool.
359  */
360 void log_print_file_data_dump(const gchar *file, const gchar *function, gint line)
361 {
362         gchar *exe_path;
363
364         exe_path = g_path_get_dirname(gq_executable_path);
365
366         log_printf("FileData dump start");
367         log_printf("%s/../%s:%d %s\n", exe_path, file, line, function);
368
369         file_data_dump();
370
371         log_printf("FileData dump end");
372
373         g_free(exe_path);
374 }
375
376 #endif /* DEBUG */
377 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */