4c1622496c10aa6dbe802cd0c40c651d0906eb54
[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, const gchar *function_name,
121                                                                         int line_number, const gchar *format, ...)
122 {
123         va_list ap;
124         gchar *message;
125         gchar *location;
126         gchar *buf;
127
128         va_start(ap, format);
129         message = g_strdup_vprintf(format, ap);
130         va_end(ap);
131
132         if (options && options->log_window.timer_data)
133                 {
134                 location = g_strdup_printf("%s:%s:%s:%d:", get_exec_time(), file_name,
135                                                                                                 function_name, line_number);
136                 }
137         else
138                 {
139                 location = g_strdup_printf("%s:%s:%d:", file_name, function_name, line_number);
140                 }
141
142         buf = g_strconcat(location, message, NULL);
143         log_domain_print_message(domain,buf);
144         g_free(location);
145         g_free(message);
146 }
147
148 void log_domain_printf(const gchar *domain, const gchar *format, ...)
149 {
150         va_list ap;
151         gchar *buf;
152
153         va_start(ap, format);
154         buf = g_strdup_vprintf(format, ap);
155         va_end(ap);
156
157         log_domain_print_message(domain, buf);
158 }
159
160 /*
161  * Debugging only functions
162  */
163
164 #ifdef DEBUG
165
166 static gint debug_level = DEBUG_LEVEL_MIN;
167
168
169 gint get_debug_level()
170 {
171         return debug_level;
172 }
173
174 void set_debug_level(gint new_level)
175 {
176         debug_level = CLAMP(new_level, DEBUG_LEVEL_MIN, DEBUG_LEVEL_MAX);
177 }
178
179 void debug_level_add(gint delta)
180 {
181         set_debug_level(debug_level + delta);
182 }
183
184 gint required_debug_level(gint level)
185 {
186         return (debug_level >= level);
187 }
188
189 static gint timeval_delta(struct timeval *result, struct timeval *x, struct timeval *y)
190 {
191         if (x->tv_usec < y->tv_usec)
192                 {
193                 gint nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
194                 y->tv_usec -= 1000000 * nsec;
195                 y->tv_sec += nsec;
196                 }
197
198         if (x->tv_usec - y->tv_usec > 1000000)
199                 {
200                 gint nsec = (x->tv_usec - y->tv_usec) / 1000000;
201                 y->tv_usec += 1000000 * nsec;
202                 y->tv_sec -= nsec;
203         }
204
205         result->tv_sec = x->tv_sec - y->tv_sec;
206         result->tv_usec = x->tv_usec - y->tv_usec;
207
208         return x->tv_sec < y->tv_sec;
209 }
210
211 const gchar *get_exec_time()
212 {
213         static gchar timestr[30];
214         static struct timeval start_tv = {0, 0};
215         static struct timeval previous = {0, 0};
216         static gint started = 0;
217
218         struct timeval tv = {0, 0};
219         static struct timeval delta = {0, 0};
220
221         gettimeofday(&tv, nullptr);
222
223         if (start_tv.tv_sec == 0) start_tv = tv;
224
225         tv.tv_sec -= start_tv.tv_sec;
226         if (tv.tv_usec >= start_tv.tv_usec)
227                 tv.tv_usec -= start_tv.tv_usec;
228         else
229                 {
230                 tv.tv_usec += 1000000 - start_tv.tv_usec;
231                 tv.tv_sec -= 1;
232                 }
233
234         if (started) timeval_delta(&delta, &tv, &previous);
235
236         previous = tv;
237         started = 1;
238
239         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));
240
241         return timestr;
242 }
243
244 void init_exec_time()
245 {
246         get_exec_time();
247 }
248
249 void set_regexp(const gchar *cmd_regexp)
250 {
251         regexp = g_strdup(cmd_regexp);
252 }
253
254 gchar *get_regexp()
255 {
256         return g_strdup(regexp);
257 }
258
259 #ifdef HAVE_EXECINFO_H
260 /**
261  * @brief Backtrace of geeqie files
262  * @param file
263  * @param function
264  * @param line
265  *
266  * Requires command line program addr2line \n
267  * Prints the contents of the backtrace buffer for Geeqie files. \n
268  * Format printed is: \n
269  * <full path to source file>:<line number>
270  *
271  * The log window F1 command and options->log_window.action may be used
272  * to open an editor at a backtrace location.
273  */
274 void log_print_backtrace(const gchar *file, const gchar *function, gint line)
275 {
276         FILE *fp;
277         char **bt_syms;
278         char path[2048];
279         gchar *address_offset;
280         gchar *cmd_line;
281         gchar *exe_path;
282         gchar *function_name = nullptr;
283         gchar *paren_end;
284         gchar *paren_start;
285         gint bt_size;
286         gint i;
287         void *bt[1024];
288
289         if (runcmd(reinterpret_cast<const gchar *>("which addr2line >/dev/null 2>&1")) == 0)
290                 {
291                 exe_path = g_path_get_dirname(gq_executable_path);
292                 bt_size = backtrace(bt, 1024);
293                 bt_syms = backtrace_symbols(bt, bt_size);
294
295                 log_printf("Backtrace start");
296                 log_printf("%s/../%s:%d %s\n", exe_path, file, line, function);
297
298                 /* Last item is always "??:?", so ignore it */
299                 for (i = 1; i < bt_size - 1; i++)
300                         {
301                         if (strstr(bt_syms[i], GQ_APPNAME_LC))
302                                 {
303                                 paren_start = g_strstr_len(bt_syms[i], -1, "(");
304                                 paren_end = g_strstr_len(bt_syms[i], -1, ")");
305                                 address_offset = g_strndup(paren_start + 1, paren_end - paren_start - 1);
306
307                                 cmd_line = g_strconcat("addr2line -p -f -C -e ", gq_executable_path, " ", address_offset, NULL);
308
309                                 fp = popen(cmd_line, "r");
310                                 if (fp == nullptr)
311                                         {
312                                         log_printf("Failed to run command: %s", cmd_line);
313                                         }
314                                 else
315                                         {
316                                         while (fgets(path, sizeof(path), fp) != nullptr)
317                                                 {
318                                                 /* Remove redundant newline */
319                                                 path[strlen(path) - 1] = '\0';
320
321                                                 if (g_strstr_len(path, strlen(path), "(") != nullptr)
322                                                         {
323                                                         function_name = g_strndup(path, g_strstr_len(path, strlen(path), "(") - path);
324                                                         }
325                                                 else
326                                                         {
327                                                         function_name = g_strdup("");
328                                                         }
329                                                 log_printf("%s %s", g_strstr_len(path, -1, "at ") + 3, function_name);
330
331                                                 g_free(function_name);
332                                                 }
333                                         }
334
335                                 pclose(fp);
336
337                                 g_free(address_offset);
338                                 g_free(cmd_line);
339                                 }
340                         }
341                 log_printf("Backtrace end");
342
343                 free(bt_syms);
344                 g_free(exe_path);
345                 }
346 }
347 #else
348 void log_print_backtrace(const gchar *, const gchar *, gint)
349 {
350 }
351 #endif
352
353 /**
354  * @brief Print ref. count and image name
355  * @param file
356  * @param function
357  * @param line
358  *
359  * Print image ref. count and full path name of all images in
360  * the file_data_pool.
361  */
362 void log_print_file_data_dump(const gchar *file, const gchar *function, gint line)
363 {
364         gchar *exe_path;
365
366         exe_path = g_path_get_dirname(gq_executable_path);
367
368         log_printf("FileData dump start");
369         log_printf("%s/../%s:%d %s\n", exe_path, file, line, function);
370
371         file_data_dump();
372
373         log_printf("FileData dump end");
374
375         g_free(exe_path);
376 }
377
378 #endif /* DEBUG */
379 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */