profile_spec.cpp revision cc2ee177dbb3befca43e36cfc56778b006c3d050
1/**
2 * @file profile_spec.cpp
3 * Contains a PP profile specification
4 *
5 * @remark Copyright 2003 OProfile authors
6 * @remark Read the file COPYING
7 *
8 * @author Philippe Elie
9 */
10
11#include <algorithm>
12#include <set>
13#include <sstream>
14#include <iterator>
15#include <iostream>
16
17#include "file_manip.h"
18#include "op_config.h"
19#include "profile_spec.h"
20#include "string_manip.h"
21#include "glob_filter.h"
22#include "locate_images.h"
23#include "op_exception.h"
24
25using namespace std;
26
27namespace {
28
29// PP:3.7, full path, or relative path. If we can't find it,
30// we should maintain the original to maintain the wordexp etc.
31string const fixup_image_spec(string const & str, extra_images const & extra)
32{
33	string dummy_archive_path;
34	// FIXME: what todo if an error in find_image_path() ?
35	image_error error;
36	return find_image_path(dummy_archive_path, str, extra, error);
37}
38
39
40void fixup_image_spec(vector<string> & images, extra_images const & extra)
41{
42	vector<string>::iterator it = images.begin();
43	vector<string>::iterator const end = images.end();
44
45	for (; it != end; ++it) {
46		*it = fixup_image_spec(*it, extra);
47	}
48}
49
50}  // anon namespace
51
52
53profile_spec::profile_spec(extra_images const & extra)
54	: extra(extra)
55{
56	parse_table["archive"] = &profile_spec::parse_archive_path;
57	parse_table["session"] = &profile_spec::parse_session;
58	parse_table["session-exclude"] =
59		&profile_spec::parse_session_exclude;
60	parse_table["image"] = &profile_spec::parse_image;
61	parse_table["image-exclude"] = &profile_spec::parse_image_exclude;
62	parse_table["lib-image"] = &profile_spec::parse_lib_image;
63	parse_table["event"] = &profile_spec::parse_event;
64	parse_table["count"] = &profile_spec::parse_count;
65	parse_table["unit-mask"] = &profile_spec::parse_unitmask;
66	parse_table["tid"] = &profile_spec::parse_tid;
67	parse_table["tgid"] = &profile_spec::parse_tgid;
68	parse_table["cpu"] = &profile_spec::parse_cpu;
69}
70
71
72void profile_spec::parse(string const & tag_value)
73{
74	string value;
75	action_t action = get_handler(tag_value, value);
76	if (!action) {
77		throw invalid_argument("profile_spec::parse(): not "
78				       "a valid tag \"" + tag_value + "\"");
79	}
80
81	(this->*action)(value);
82}
83
84
85bool profile_spec::is_valid_tag(string const & tag_value)
86{
87	string value;
88	return get_handler(tag_value, value);
89}
90
91
92void profile_spec::set_image_or_lib_name(string const & str)
93{
94	/* FIXME: what does spec say about this being allowed to be
95	 * a comma list or not ? */
96	image_or_lib_image.push_back(fixup_image_spec(str, extra));
97}
98
99
100void profile_spec::parse_archive_path(string const & str)
101{
102	archive_path = op_realpath(str);
103}
104
105
106string profile_spec::get_archive_path() const
107{
108	return archive_path;
109}
110
111
112void profile_spec::parse_session(string const & str)
113{
114	session = separate_token(str, ',');
115}
116
117
118void profile_spec::parse_session_exclude(string const & str)
119{
120	session_exclude = separate_token(str, ',');
121}
122
123
124void profile_spec::parse_image(string const & str)
125{
126	image = separate_token(str, ',');
127	fixup_image_spec(image, extra);
128}
129
130
131void profile_spec::parse_image_exclude(string const & str)
132{
133	image_exclude = separate_token(str, ',');
134}
135
136
137void profile_spec::parse_lib_image(string const & str)
138{
139	lib_image = separate_token(str, ',');
140	fixup_image_spec(image, extra);
141}
142
143
144void profile_spec::parse_event(string const & str)
145{
146	event.set(str);
147}
148
149
150void profile_spec::parse_count(string const & str)
151{
152	count.set(str);
153}
154
155
156void profile_spec::parse_unitmask(string const & str)
157{
158	unitmask.set(str);
159}
160
161
162void profile_spec::parse_tid(string const & str)
163{
164	tid.set(str);
165}
166
167
168void profile_spec::parse_tgid(string const & str)
169{
170	tgid.set(str);
171}
172
173
174void profile_spec::parse_cpu(string const & str)
175{
176	cpu.set(str);
177}
178
179
180profile_spec::action_t
181profile_spec::get_handler(string const & tag_value, string & value)
182{
183	string::size_type pos = tag_value.find_first_of(':');
184	if (pos == string::npos) {
185		return 0;
186	}
187
188	string tag(tag_value.substr(0, pos));
189	value = tag_value.substr(pos + 1);
190
191	parse_table_t::const_iterator it = parse_table.find(tag);
192	if (it == parse_table.end()) {
193		return 0;
194	}
195
196	return it->second;
197}
198
199
200namespace {
201
202/// return true if the value from the profile spec may match the comma
203/// list
204template<typename T>
205bool comma_match(comma_list<T> const & cl, generic_spec<T> const & value)
206{
207	// if the profile spec is "all" we match the sample file
208	if (!cl.is_set())
209		return true;
210
211	// an "all" sample file should never match specified profile
212	// spec values
213	if (!value.is_set())
214		return false;
215
216	// now match each profile spec value against the sample file
217	return cl.match(value.value());
218}
219
220}
221
222
223bool profile_spec::match(filename_spec const & spec) const
224{
225	bool matched_by_image_or_lib_image = false;
226
227	// PP:3.19
228	if (!image_or_lib_image.empty()) {
229		// Need the path search for the benefit of modules
230		// which have "/oprofile" or similar
231		string simage = fixup_image_spec(spec.image, extra);
232		string slib_image = fixup_image_spec(spec.lib_image, extra);
233		glob_filter filter(image_or_lib_image, image_exclude);
234		if (filter.match(simage) || filter.match(slib_image)) {
235			matched_by_image_or_lib_image = true;
236		}
237	}
238
239	if (!matched_by_image_or_lib_image) {
240		// PP:3.7 3.8
241		if (!image.empty()) {
242			glob_filter filter(image, image_exclude);
243			if (!filter.match(spec.image)) {
244				return false;
245			}
246		} else if (!image_or_lib_image.empty()) {
247			// image.empty() means match all except if user
248			// specified image_or_lib_image
249			return false;
250		}
251
252		// PP:3.9 3.10
253		if (!lib_image.empty()) {
254			glob_filter filter(lib_image, image_exclude);
255			if (!filter.match(spec.lib_image)) {
256				return false;
257			}
258		} else if (image.empty() && !image_or_lib_image.empty()) {
259			// lib_image empty means match all except if user
260			// specified image_or_lib_image *or* we already
261			// matched this spec through image
262			return false;
263		}
264	}
265
266	if (!matched_by_image_or_lib_image) {
267		// if we don't match by image_or_lib_image we must try to
268		// exclude from spec, exclusion from image_or_lib_image has
269		// been handled above
270		vector<string> empty;
271		glob_filter filter(empty, image_exclude);
272		if (!filter.match(spec.image)) {
273			return false;
274		}
275		if (!spec.lib_image.empty() && !filter.match(spec.lib_image)) {
276			return false;
277		}
278	}
279
280	if (!event.match(spec.event))
281		return false;
282
283	if (!count.match(spec.count))
284		return false;
285
286	if (!unitmask.match(spec.unitmask))
287		return false;
288
289	if (!comma_match(cpu, spec.cpu))
290		return false;
291
292	if (!comma_match(tid, spec.tid))
293		return false;
294
295	if (!comma_match(tgid, spec.tgid))
296		return false;
297
298	return true;
299}
300
301
302profile_spec profile_spec::create(list<string> const & args,
303                                  extra_images const & extra)
304{
305	profile_spec spec(extra);
306	set<string> tag_seen;
307
308	list<string>::const_iterator it = args.begin();
309	list<string>::const_iterator end = args.end();
310
311	for (; it != end; ++it) {
312		if (spec.is_valid_tag(*it)) {
313			if (tag_seen.find(*it) != tag_seen.end()) {
314				throw op_runtime_error("tag specified "
315				       "more than once: " + *it);
316			}
317			tag_seen.insert(*it);
318			spec.parse(*it);
319		} else {
320			string const file = op_realpath(*it);
321			spec.set_image_or_lib_name(file);
322		}
323	}
324
325	// PP:3.5 no session given means use the current session.
326	if (spec.session.empty()) {
327		spec.session.push_back("current");
328	}
329
330	return spec;
331}
332
333namespace {
334
335vector<string> filter_session(vector<string> const & session,
336			      vector<string> const & session_exclude)
337{
338	vector<string> result(session);
339
340	if (result.empty()) {
341		result.push_back("current");
342	}
343
344	for (size_t i = 0 ; i < session_exclude.size() ; ++i) {
345		// FIXME: would we use fnmatch on each item, are we allowed
346		// to --session=current* ?
347		vector<string>::iterator it =
348			find(result.begin(), result.end(), session_exclude[i]);
349
350		if (it != result.end()) {
351			result.erase(it);
352		}
353	}
354
355	return result;
356}
357
358
359bool valid_candidate(string const & base_dir, string const & filename,
360                     profile_spec const & spec, bool exclude_dependent,
361                     bool exclude_cg)
362{
363	if (exclude_cg && filename.find("{cg}") != string::npos)
364		return false;
365
366	// strip out non sample files
367	string const & sub = filename.substr(base_dir.size(), string::npos);
368	if (!is_prefix(sub, "/{root}/") && !is_prefix(sub, "/{kern}/"))
369		return false;
370
371	filename_spec file_spec(filename);
372	if (spec.match(file_spec)) {
373		if (exclude_dependent && file_spec.is_dependent())
374			return false;
375		return true;
376	}
377
378	return false;
379}
380
381}  // anonymous namespace
382
383
384list<string> profile_spec::generate_file_list(bool exclude_dependent,
385  bool exclude_cg) const
386{
387	// FIXME: isn't remove_duplicates faster than doing this, then copy() ?
388	set<string> unique_files;
389
390	vector<string> sessions = filter_session(session, session_exclude);
391
392	if (sessions.empty()) {
393		ostringstream os;
394		os << "No session given\n"
395		   << "included session was:\n";
396		copy(session.begin(), session.end(),
397		     ostream_iterator<string>(os, "\n"));
398		os << "excluded session was:\n";
399		copy(session_exclude.begin(), session_exclude.end(),
400		     ostream_iterator<string>(os, "\n"));
401		throw invalid_argument(os.str());
402	}
403
404	bool found_file = false;
405
406	vector<string>::const_iterator cit = sessions.begin();
407	vector<string>::const_iterator end = sessions.end();
408
409	for (; cit != end; ++cit) {
410		if (cit->empty())
411			continue;
412
413		string base_dir;
414		if ((*cit)[0] != '.' && (*cit)[0] != '/')
415			base_dir = archive_path + OP_SAMPLES_DIR;
416		base_dir += *cit;
417
418		base_dir = op_realpath(base_dir);
419
420		list<string> files;
421		create_file_list(files, base_dir, "*", true);
422
423		if (!files.empty())
424			found_file = true;
425
426		list<string>::const_iterator it = files.begin();
427		list<string>::const_iterator fend = files.end();
428		for (; it != fend; ++it) {
429			if (valid_candidate(base_dir, *it, *this,
430			    exclude_dependent, exclude_cg)) {
431				unique_files.insert(*it);
432			}
433		}
434	}
435
436	if (!found_file) {
437		ostringstream os;
438		os  << "No sample file found: try running opcontrol --dump\n"
439		    << "or specify a session containing sample files\n";
440		throw op_fatal_error(os.str());
441	}
442
443	list<string> result;
444	copy(unique_files.begin(), unique_files.end(), back_inserter(result));
445
446	return result;
447}
448