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