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