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