1 #include <config.h>
2 #include <mono/utils/mono-logger-internals.h>
3 #include <mono/utils/mono-proclib.h>
4 #include "log.h"
5
6 #ifdef HAVE_UNISTD_H
7 #include <unistd.h>
8 #endif
9
10 typedef struct {
11 const char *event_name;
12 const int mask;
13 const int compat_mask;
14 } NameAndMask;
15
16 static NameAndMask event_list[] = {
17 { "exception", PROFLOG_EXCEPTION_EVENTS },
18 { "monitor", PROFLOG_MONITOR_EVENTS },
19 { "gc", PROFLOG_GC_EVENTS },
20 { "gcalloc", PROFLOG_GC_ALLOCATION_EVENTS },
21 { "gcmove", PROFLOG_GC_MOVE_EVENTS },
22 { "gcroot", PROFLOG_GC_ROOT_EVENTS },
23 { "gchandle", PROFLOG_GC_HANDLE_EVENTS },
24 { "finalization", PROFLOG_GC_FINALIZATION_EVENTS },
25 { "counter", PROFLOG_COUNTER_EVENTS },
26 { "jit", PROFLOG_JIT_EVENTS },
27
28 { "counters", PROFLOG_COUNTER_EVENTS },
29 { "alloc", PROFLOG_ALLOC_ALIAS, PROFLOG_ALLOC_ALIAS | PROFLOG_GC_ROOT_EVENTS },
30 { "legacy", PROFLOG_LEGACY_ALIAS },
31 };
32
33 static void usage (void);
34 static void set_hsmode (ProfilerConfig *config, const char* val);
35 static void set_sample_freq (ProfilerConfig *config, const char *val);
36
37 static gboolean
match_option(const char * arg,const char * opt_name,const char ** rval)38 match_option (const char *arg, const char *opt_name, const char **rval)
39 {
40 if (rval) {
41 const char *end = strchr (arg, '=');
42
43 *rval = NULL;
44 if (!end)
45 return !strcmp (arg, opt_name);
46
47 if (strncmp (arg, opt_name, strlen (opt_name)) || (end - arg) > strlen (opt_name) + 1)
48 return FALSE;
49 *rval = end + 1;
50 return TRUE;
51 } else {
52 //FIXME how should we handle passing a value to an arg that doesn't expect it?
53 return !strcmp (arg, opt_name);
54 }
55 }
56
57 static gboolean compat_args_parsing;
58
59 static void
parse_arg(const char * arg,ProfilerConfig * config)60 parse_arg (const char *arg, ProfilerConfig *config)
61 {
62 const char *val;
63
64 static gboolean first_processed;
65
66 if (!first_processed) {
67 first_processed = TRUE;
68 if (match_option (arg, "nodefaults", NULL)) {
69 //enables new style of default events, IE, nothing.
70 return;
71 } else {
72 config->enable_mask = PROFLOG_EXCEPTION_EVENTS | PROFLOG_COUNTER_EVENTS;
73 config->always_do_root_report = TRUE;
74 compat_args_parsing = TRUE;
75 }
76 }
77
78 if (match_option (arg, "help", NULL)) {
79 usage ();
80 } else if (match_option (arg, "nodefaults", NULL)) {
81 mono_profiler_printf_err ("nodefaults can only be used as the first argument");
82 } else if (match_option (arg, "report", NULL)) {
83 config->do_report = TRUE;
84 } else if (match_option (arg, "debug", NULL)) {
85 config->do_debug = TRUE;
86 } else if (match_option (arg, "heapshot", &val)) {
87 set_hsmode (config, val);
88 if (config->hs_mode != MONO_PROFILER_HEAPSHOT_NONE) {
89 config->enable_mask |= PROFLOG_HEAPSHOT_ALIAS;
90 if (compat_args_parsing)
91 config->enable_mask |= PROFLOG_GC_MOVE_EVENTS;
92 }
93 } else if (match_option (arg, "heapshot-on-shutdown", NULL)) {
94 config->hs_on_shutdown = TRUE;
95 config->enable_mask |= PROFLOG_HEAPSHOT_ALIAS;
96 } else if (match_option (arg, "sample", &val)) {
97 set_sample_freq (config, val);
98 config->sampling_mode = MONO_PROFILER_SAMPLE_MODE_PROCESS;
99 config->enable_mask |= PROFLOG_SAMPLE_EVENTS;
100 } else if (match_option (arg, "sample-real", &val) || (compat_args_parsing && match_option (arg, "sampling-real", &val))) {
101 set_sample_freq (config, val);
102 config->sampling_mode = MONO_PROFILER_SAMPLE_MODE_REAL;
103 config->enable_mask |= PROFLOG_SAMPLE_EVENTS;
104 } else if (match_option (arg, "calls", NULL)) {
105 config->enter_leave = TRUE;
106 } else if (match_option (arg, "nocalls", NULL)) {
107 if (!compat_args_parsing)
108 mono_profiler_printf_err ("Could not parse argument: %s", arg);
109 } else if (match_option (arg, "coverage", NULL)) {
110 g_warning ("the log profiler support for code coverage is obsolete, use the \"coverage\" profiler");
111 config->collect_coverage = TRUE;
112 } else if (match_option (arg, "zip", NULL)) {
113 config->use_zip = TRUE;
114 } else if (match_option (arg, "output", &val)) {
115 config->output_filename = g_strdup (val);
116 } else if (match_option (arg, "port", &val)) {
117 char *end;
118 config->command_port = strtoul (val, &end, 10);
119 } else if (match_option (arg, "maxframes", &val)) {
120 char *end;
121 int num_frames = strtoul (val, &end, 10);
122 if (num_frames > MAX_FRAMES)
123 num_frames = MAX_FRAMES;
124 config->num_frames = num_frames;
125 } else if (match_option (arg, "maxsamples", &val)) {
126 char *end;
127 int max_samples = strtoul (val, &end, 10);
128 if (max_samples)
129 config->max_allocated_sample_hits = max_samples;
130 } else if (match_option (arg, "calldepth", &val)) {
131 char *end;
132 config->max_call_depth = strtoul (val, &end, 10);
133 } else if (match_option (arg, "callspec", &val)) {
134 if (!val)
135 val = "";
136 if (val[0] == '\"')
137 ++val;
138 char *spec = g_strdup (val);
139 size_t speclen = strlen (val);
140 if (speclen > 0 && spec[speclen - 1] == '\"')
141 spec[speclen - 1] = '\0';
142 char *errstr;
143 if (!mono_callspec_parse (spec, &config->callspec, &errstr)) {
144 mono_profiler_printf_err (
145 "Could not parse callspec: '%s': %s", spec,
146 errstr);
147 g_free (errstr);
148 mono_callspec_cleanup (&config->callspec);
149 }
150 g_free (spec);
151 } else if (match_option (arg, "covfilter-file", &val)) {
152 if (config->cov_filter_files == NULL)
153 config->cov_filter_files = g_ptr_array_new ();
154 g_ptr_array_add (config->cov_filter_files, g_strdup (val));
155 } else {
156 int i;
157
158 for (i = 0; i < G_N_ELEMENTS (event_list); ++i){
159 int mask = event_list [i].mask;
160 if (compat_args_parsing && event_list [i].compat_mask)
161 mask = event_list [i].compat_mask;
162 if (!strcmp (arg, event_list [i].event_name)) {
163 config->enable_mask |= mask;
164 break;
165 } else if (!strncmp (arg, "no", 2) && !strcmp (arg + 2, event_list [i].event_name)) {
166 config->disable_mask |= mask;
167 break;
168 }
169 }
170
171 if (i == G_N_ELEMENTS (event_list))
172 mono_profiler_printf_err ("Could not parse argument: %s", arg);
173 }
174 }
175
176 static void
load_args_from_env_or_default(ProfilerConfig * config)177 load_args_from_env_or_default (ProfilerConfig *config)
178 {
179 //XXX change this to header constants
180
181 config->max_allocated_sample_hits = mono_cpu_count () * 1000;
182 config->sampling_mode = MONO_PROFILER_SAMPLE_MODE_NONE;
183 config->sample_freq = 100;
184 config->max_call_depth = 100;
185 config->num_frames = MAX_FRAMES;
186 }
187
188
189 void
proflog_parse_args(ProfilerConfig * config,const char * desc)190 proflog_parse_args (ProfilerConfig *config, const char *desc)
191 {
192 const char *p;
193 gboolean in_quotes = FALSE;
194 char quote_char = '\0';
195 char *buffer = malloc (strlen (desc));
196 int buffer_pos = 0;
197
198 load_args_from_env_or_default (config);
199
200 for (p = desc; *p; p++){
201 switch (*p){
202 case ',':
203 if (!in_quotes) {
204 if (buffer_pos != 0){
205 buffer [buffer_pos] = 0;
206 parse_arg (buffer, config);
207 buffer_pos = 0;
208 }
209 } else {
210 buffer [buffer_pos++] = *p;
211 }
212 break;
213
214 case '\\':
215 if (p [1]) {
216 buffer [buffer_pos++] = p[1];
217 p++;
218 }
219 break;
220 case '\'':
221 case '"':
222 if (in_quotes) {
223 if (quote_char == *p)
224 in_quotes = FALSE;
225 else
226 buffer [buffer_pos++] = *p;
227 } else {
228 in_quotes = TRUE;
229 quote_char = *p;
230 }
231 break;
232 default:
233 buffer [buffer_pos++] = *p;
234 break;
235 }
236 }
237
238 if (buffer_pos != 0) {
239 buffer [buffer_pos] = 0;
240 parse_arg (buffer, config);
241 }
242
243 g_free (buffer);
244
245 //Compure config effective mask
246 config->effective_mask = config->enable_mask & ~config->disable_mask;
247 }
248
249 static void
set_hsmode(ProfilerConfig * config,const char * val)250 set_hsmode (ProfilerConfig *config, const char* val)
251 {
252 if (!val) {
253 config->hs_mode = MONO_PROFILER_HEAPSHOT_MAJOR;
254 return;
255 }
256
257 if (strcmp (val, "ondemand") == 0) {
258 config->hs_mode = MONO_PROFILER_HEAPSHOT_ON_DEMAND;
259 return;
260 }
261
262 char *end;
263
264 unsigned int count = strtoul (val, &end, 10);
265
266 if (val == end) {
267 mono_profiler_printf_err ("Could not parse heapshot mode");
268 return;
269 }
270
271 if (strcmp (end, "ms") == 0) {
272 config->hs_mode = MONO_PROFILER_HEAPSHOT_X_MS;
273 config->hs_freq_ms = count;
274 } else if (strcmp (end, "gc") == 0) {
275 config->hs_mode = MONO_PROFILER_HEAPSHOT_X_GC;
276 config->hs_freq_gc = count;
277 } else
278 mono_profiler_printf_err ("Could not parse heapshot mode");
279 }
280
281 static void
set_sample_freq(ProfilerConfig * config,const char * val)282 set_sample_freq (ProfilerConfig *config, const char *val)
283 {
284 int freq;
285
286 if (!val)
287 return;
288
289 const char *p = val;
290
291 // Is it only the frequency (new option style)?
292 if (isdigit (*p))
293 goto parse;
294
295 // Skip the sample type for backwards compatibility.
296 while (isalpha (*p))
297 p++;
298
299 // Skip the forward slash only if we got a sample type.
300 if (p != val && *p == '/') {
301 p++;
302
303 char *end;
304
305 parse:
306 freq = strtoul (p, &end, 10);
307
308 if (p == end)
309 mono_profiler_printf_err ("Could not parse sample frequency");
310 else
311 config->sample_freq = freq;
312 }
313 }
314
315 static void
usage(void)316 usage (void)
317 {
318 mono_profiler_printf ("Mono log profiler version %d.%d (format: %d)", LOG_VERSION_MAJOR, LOG_VERSION_MINOR, LOG_DATA_VERSION);
319 mono_profiler_printf ("Usage: mono --profile=log[:OPTION1[,OPTION2...]] program.exe\n");
320 mono_profiler_printf ("Options:");
321 mono_profiler_printf ("\thelp show this usage info");
322 mono_profiler_printf ("\t[no]'EVENT' enable/disable an individual profiling event");
323 mono_profiler_printf ("\t valid EVENT values:");
324
325 for (int i = 0; i < G_N_ELEMENTS (event_list); i++)
326 mono_profiler_printf ("\t %s", event_list [i].event_name);
327
328 mono_profiler_printf ("\tnodefaults disable legacy rules for enabling extra events");
329 mono_profiler_printf ("\t[no]alloc enable/disable recording allocation info");
330 mono_profiler_printf ("\t[no]legacy enable/disable pre Mono 5.6 default profiler events");
331 mono_profiler_printf ("\tsample[-real][=FREQ] enable/disable statistical sampling of threads");
332 mono_profiler_printf ("\t FREQ in Hz, 100 by default");
333 mono_profiler_printf ("\t the -real variant uses wall clock time instead of process time");
334 mono_profiler_printf ("\theapshot[=MODE] record heapshot info (by default at each major collection)");
335 mono_profiler_printf ("\t MODE: every XXms milliseconds, every YYgc collections, ondemand");
336 mono_profiler_printf ("\theapshot-on-shutdown do a heapshot on runtime shutdown");
337 mono_profiler_printf ("\t this option is independent of the above option");
338 mono_profiler_printf ("\tcalls enable recording enter/leave method events (very heavy)");
339 mono_profiler_printf ("\tmaxframes=NUM collect up to NUM stack frames");
340 mono_profiler_printf ("\tcalldepth=NUM ignore method events for call chain depth bigger than NUM");
341 mono_profiler_printf ("\toutput=FILENAME write the data to file FILENAME (the file is always overwritten)");
342 mono_profiler_printf ("\toutput=+FILENAME write the data to file FILENAME.pid (the file is always overwritten)");
343 mono_profiler_printf ("\toutput=|PROGRAM write the data to the stdin of PROGRAM");
344 mono_profiler_printf ("\t %%t is substituted with date and time, %%p with the pid");
345 mono_profiler_printf ("\treport create a report instead of writing the raw data to a file");
346 mono_profiler_printf ("\tzip compress the output data");
347 mono_profiler_printf ("\tport=PORTNUM use PORTNUM for the listening command server");
348
349 exit (0);
350 }
351