Commit | Line | Data |
---|---|---|
c72a561f JG |
1 | # The MIT License (MIT) |
2 | # | |
3 | # Copyright (c) 2013-2017 Jérémie Galarneau <jeremie.galarneau@efficios.com> | |
4 | # | |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | # of this software and associated documentation files (the "Software"), to deal | |
7 | # in the Software without restriction, including without limitation the rights | |
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
9 | # copies of the Software, and to permit persons to whom the Software is | |
10 | # furnished to do so, subject to the following conditions: | |
11 | # | |
12 | # The above copyright notice and this permission notice shall be included in | |
13 | # all copies or substantial portions of the Software. | |
14 | # | |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
21 | # THE SOFTWARE. | |
22 | ||
23 | import bt2 | |
24 | import babeltrace.common as common | |
25 | from babeltrace import reader_trace_handle | |
26 | from babeltrace import reader_event | |
27 | import os | |
28 | ||
29 | ||
30 | class TraceCollection: | |
31 | """ | |
32 | A :class:`TraceCollection` is a collection of opened traces. | |
33 | ||
34 | Once a trace collection is created, you can add traces to the | |
35 | collection by using the :meth:`add_trace` or | |
36 | :meth:`add_traces_recursive`, and then iterate on the merged | |
37 | events using :attr:`events`. | |
38 | ||
39 | You may use :meth:`remove_trace` to close and remove a specific | |
40 | trace from a trace collection. | |
41 | """ | |
42 | ||
43 | def __init__(self, intersect_mode=False): | |
44 | """ | |
45 | Creates an empty trace collection. | |
46 | """ | |
47 | ||
48 | self._intersect_mode = intersect_mode | |
49 | self._trace_handles = set() | |
50 | self._next_th_id = 0 | |
51 | self._ctf_plugin = bt2.find_plugin('ctf') | |
52 | assert(self._ctf_plugin is not None) | |
53 | self._fs_comp_cls = self._ctf_plugin.source_component_classes['fs'] | |
54 | ||
55 | def add_trace(self, path, format_str): | |
56 | """ | |
57 | Adds a trace to the trace collection. | |
58 | ||
59 | *path* is the exact path of the trace on the filesystem. | |
60 | ||
61 | *format_str* is a string indicating the type of trace to | |
62 | add. ``ctf`` is currently the only supported trace format. | |
63 | ||
64 | Returns the corresponding :class:`TraceHandle` instance for | |
65 | this opened trace on success, or ``None`` on error. | |
66 | ||
67 | This function **does not** recurse directories to find a | |
68 | trace. See :meth:`add_traces_recursive` for a recursive | |
69 | version of this function. | |
70 | """ | |
71 | ||
72 | if format_str != 'ctf': | |
73 | raise ValueError('only the "ctf" format is supported') | |
74 | ||
75 | if not os.path.isfile(os.path.join(path, 'metadata')): | |
76 | raise ValueError('no "metadata" file found in "{}"'.format(path)) | |
77 | ||
78 | th = reader_trace_handle.TraceHandle.__new__(reader_trace_handle.TraceHandle) | |
79 | th._id = self._next_th_id | |
80 | self._next_th_id += 1 | |
81 | th._trace_collection = self | |
82 | th._path = path | |
83 | self._trace_handles.add(th) | |
84 | return th | |
85 | ||
86 | def add_traces_recursive(self, path, format_str): | |
87 | """ | |
88 | Adds traces to this trace collection by recursively searching | |
89 | in the *path* directory. | |
90 | ||
91 | *format_str* is a string indicating the type of trace to add. | |
92 | ``ctf`` is currently the only supported trace format. | |
93 | ||
94 | Returns a :class:`dict` object mapping full paths to trace | |
95 | handles for each trace found, or ``None`` on error. | |
96 | ||
97 | See also :meth:`add_trace`. | |
98 | """ | |
99 | ||
100 | trace_handles = {} | |
101 | noTrace = True | |
102 | error = False | |
103 | ||
104 | for fullpath, dirs, files in os.walk(path): | |
105 | if "metadata" in files: | |
106 | trace_handle = self.add_trace(fullpath, format_str) | |
107 | ||
108 | if trace_handle is None: | |
109 | error = True | |
110 | continue | |
111 | ||
112 | trace_handles[fullpath] = trace_handle | |
113 | noTrace = False | |
114 | ||
115 | if noTrace and error: | |
116 | return None | |
117 | ||
118 | return trace_handles | |
119 | ||
120 | def remove_trace(self, handle): | |
121 | """ | |
122 | Removes a trace from the trace collection using its trace | |
123 | handle *trace_handle*. | |
124 | ||
125 | :class:`TraceHandle` objects are returned by :meth:`add_trace` | |
126 | and :meth:`add_traces_recursive`. | |
127 | """ | |
128 | ||
129 | if not isinstance(handle, reader_trace_handle.TraceHandle): | |
130 | raise TypeError("'{}' is not a 'TraceHandle' object".format( | |
131 | handle.__class__.__name__)) | |
132 | ||
133 | # this can raise but it would mean the trace handle is not part | |
134 | # of this trace collection anyway | |
135 | self._trace_handles.remove(handle) | |
136 | ||
137 | @property | |
138 | def intersect_mode(self): | |
139 | return self._intersect_mode | |
140 | ||
141 | @property | |
142 | def has_intersection(self): | |
143 | return any(th._has_intersection for th in self._trace_handles) | |
144 | ||
145 | @property | |
146 | def events(self): | |
147 | """ | |
148 | Generates the ordered :class:`Event` objects of all the opened | |
149 | traces contained in this trace collection. | |
150 | """ | |
151 | ||
152 | return self._gen_events() | |
153 | ||
154 | def events_timestamps(self, timestamp_begin, timestamp_end): | |
155 | """ | |
156 | Generates the ordered :class:`Event` objects of all the opened | |
157 | traces contained in this trace collection from *timestamp_begin* | |
158 | to *timestamp_end*. | |
159 | ||
160 | *timestamp_begin* and *timestamp_end* are given in nanoseconds | |
161 | since Epoch. | |
162 | ||
163 | See :attr:`events` for notes and limitations. | |
164 | """ | |
165 | ||
166 | return self._gen_events(timestamp_begin / 1e9, timestamp_end / 1e9) | |
167 | ||
168 | def _gen_events(self, begin_s=None, end_s=None): | |
3d60267b | 169 | specs = [bt2.ComponentSpec('ctf', 'fs', th.path) for th in self._trace_handles] |
c72a561f JG |
170 | |
171 | try: | |
172 | iter_cls = bt2.TraceCollectionNotificationIterator | |
173 | tc_iter = iter_cls(specs, | |
174 | stream_intersection_mode=self._intersect_mode, | |
175 | begin=begin_s, end=end_s, | |
176 | notification_types=[bt2.EventNotification]) | |
177 | return map(reader_event._create_event, tc_iter) | |
178 | except: | |
179 | raise ValueError | |
180 | ||
181 | @property | |
182 | def timestamp_begin(self): | |
183 | """ | |
184 | Begin timestamp of this trace collection (nanoseconds since Epoch). | |
185 | """ | |
186 | ||
187 | if not self._trace_handles: | |
188 | return | |
189 | ||
190 | return min(th.timestamp_begin for th in self._trace_handles) | |
191 | ||
192 | @property | |
193 | def timestamp_end(self): | |
194 | """ | |
195 | End timestamp of this trace collection (nanoseconds since Epoch). | |
196 | """ | |
197 | ||
198 | if not self._trace_handles: | |
199 | return | |
200 | ||
201 | return max(th.timestamp_end for th in self._trace_handles) |