Merge branch 'master' into lttng_2_0_control_dev
[deliverable/tracecompass.git] / org.eclipse.linuxtools.lttng.ui / src / org / eclipse / linuxtools / lttng / ui / views / control / service / LTTngControlService.java
1 /**********************************************************************
2 * Copyright (c) 2012 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Bernd Hufmann - Initial API and implementation
11 **********************************************************************/
12 package org.eclipse.linuxtools.lttng.ui.views.control.service;
13
14 import java.util.ArrayList;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19
20 import org.eclipse.core.commands.ExecutionException;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.NullProgressMonitor;
23 import org.eclipse.linuxtools.lttng.ui.views.control.Messages;
24 import org.eclipse.linuxtools.lttng.ui.views.control.model.IBaseEventInfo;
25 import org.eclipse.linuxtools.lttng.ui.views.control.model.IChannelInfo;
26 import org.eclipse.linuxtools.lttng.ui.views.control.model.IDomainInfo;
27 import org.eclipse.linuxtools.lttng.ui.views.control.model.IEventInfo;
28 import org.eclipse.linuxtools.lttng.ui.views.control.model.ISessionInfo;
29 import org.eclipse.linuxtools.lttng.ui.views.control.model.IUstProviderInfo;
30 import org.eclipse.linuxtools.lttng.ui.views.control.model.LogLevelType;
31 import org.eclipse.linuxtools.lttng.ui.views.control.model.TraceLogLevel;
32 import org.eclipse.linuxtools.lttng.ui.views.control.model.impl.BaseEventInfo;
33 import org.eclipse.linuxtools.lttng.ui.views.control.model.impl.ChannelInfo;
34 import org.eclipse.linuxtools.lttng.ui.views.control.model.impl.DomainInfo;
35 import org.eclipse.linuxtools.lttng.ui.views.control.model.impl.EventInfo;
36 import org.eclipse.linuxtools.lttng.ui.views.control.model.impl.SessionInfo;
37 import org.eclipse.linuxtools.lttng.ui.views.control.model.impl.UstProviderInfo;
38
39 /**
40 * <b><u>LTTngControlService</u></b>
41 * <p>
42 * Service for sending LTTng trace control commands to remote host.
43 * </p>
44 */
45 public class LTTngControlService implements ILttngControlService {
46 // ------------------------------------------------------------------------
47 // Constants
48 // ------------------------------------------------------------------------
49 // Command constants
50 /**
51 * The lttng tools command.
52 */
53 private final static String CONTROL_COMMAND = "lttng"; //$NON-NLS-1$
54 /**
55 * Command: lttng list.
56 */
57 private final static String COMMAND_LIST = CONTROL_COMMAND + " list "; //$NON-NLS-1$
58 /**
59 * Command to list kernel tracer information.
60 */
61 private final static String COMMAND_LIST_KERNEL = COMMAND_LIST + "-k"; //$NON-NLS-1$
62 /**
63 * Command to list user space trace information.
64 */
65 private final static String COMMAND_LIST_UST = COMMAND_LIST + "-u"; //$NON-NLS-1$
66 /**
67 * Command to create a session.
68 */
69 private final static String COMMAND_CREATE_SESSION = CONTROL_COMMAND + " create "; //$NON-NLS-1$
70 /**
71 * Command to destroy a session.
72 */
73 private final static String COMMAND_DESTROY_SESSION = CONTROL_COMMAND + " destroy "; //$NON-NLS-1$
74 /**
75 * Command to destroy a session.
76 */
77 private final static String COMMAND_START_SESSION = CONTROL_COMMAND + " start "; //$NON-NLS-1$
78 /**
79 * Command to destroy a session.
80 */
81 private final static String COMMAND_STOP_SESSION = CONTROL_COMMAND + " stop "; //$NON-NLS-1$
82 /**
83 * Command to enable a channel.
84 */
85 private final static String COMMAND_ENABLE_CHANNEL = CONTROL_COMMAND + " enable-channel "; //$NON-NLS-1$
86 /**
87 * Command to disable a channel.
88 */
89 private final static String COMMAND_DISABLE_CHANNEL = CONTROL_COMMAND + " disable-channel "; //$NON-NLS-1$
90 /**
91 * Command to enable a event.
92 */
93 private final static String COMMAND_ENABLE_EVENT = CONTROL_COMMAND + " enable-event "; //$NON-NLS-1$
94 /**
95 * Command to disable a event.
96 */
97 private final static String COMMAND_DISABLE_EVENT = CONTROL_COMMAND + " disable-event "; //$NON-NLS-1$
98
99 // Command options constants
100 /**
101 * Command line option for kernel tracer.
102 */
103 private final static String OPTION_KERNEL = " -k "; //$NON-NLS-1$
104 /**
105 * Command line option for UST tracer.
106 */
107 private final static String OPTION_UST = " -u "; //$NON-NLS-1$
108 /**
109 * Command line option for specifying a session.
110 */
111 private final static String OPTION_SESSION = " -s "; //$NON-NLS-1$
112 /**
113 * Command line option for specifying a channel.
114 */
115 private final static String OPTION_CHANNEL = " -c "; //$NON-NLS-1$
116 /**
117 * Command line option for specifying all events.
118 */
119 private final static String OPTION_ALL = " -a "; //$NON-NLS-1$
120 /**
121 * Command line option for specifying tracepoint events.
122 */
123 private final static String OPTION_TRACEPOINT = " --tracepoint "; //$NON-NLS-1$
124 /**
125 * Command line option for specifying syscall events.
126 */
127 private final static String OPTION_SYSCALL = " --syscall "; //$NON-NLS-1$
128 /**
129 * Command line option for specifying a dynamic probe.
130 */
131 private final static String OPTION_PROBE = " --probe "; //$NON-NLS-1$
132 /**
133 * Command line option for specifying a dynamic function entry/return probe.
134 */
135 private final static String OPTION_FUNCTION_PROBE = " --function "; //$NON-NLS-1$
136 /**
137 * Command line option for specifying a log level range.
138 */
139 private final static String OPTION_LOGLEVEL = " --loglevel "; //$NON-NLS-1$
140 /**
141 * Command line option for specifying a specific log level.
142 */
143 private final static String OPTION_LOGLEVEL_ONLY = " --loglevel-only "; //$NON-NLS-1$
144 /**
145 * Optional command line option for configuring a channel's overwrite mode.
146 */
147 private final static String OPTION_OVERWRITE = " --overwrite "; //$NON-NLS-1$
148 /**
149 * Optional command line option for configuring a channel's number of sub buffers.
150 */
151 private final static String OPTION_NUM_SUB_BUFFERS = " --num-subbuf "; //$NON-NLS-1$
152 /**
153 * Optional command line option for configuring a channel's sub buffer size.
154 */
155 private final static String OPTION_SUB_BUFFER_SIZE = " --subbuf-size "; //$NON-NLS-1$
156 /**
157 * Optional command line option for configuring a channel's switch timer interval.
158 */
159 private final static String OPTION_SWITCH_TIMER = " --switch-timer "; //$NON-NLS-1$
160 /**
161 * Optional command line option for configuring a channel's read timer interval.
162 */
163 private final static String OPTION_READ_TIMER = " --read-timer "; //$NON-NLS-1$
164
165 // Parsing constants
166 /**
167 * Pattern to match for error output
168 */
169 private final static Pattern ERROR_PATTERN = Pattern.compile("\\s*Error\\:.*"); //$NON-NLS-1$
170 /**
171 * Pattern to match for session information (lttng list)
172 */
173 private final static Pattern SESSION_PATTERN = Pattern.compile("\\s+(\\d+)\\)\\s+(.*)\\s+\\((.*)\\)\\s+\\[(active|inactive)\\].*"); //$NON-NLS-1$
174 /**
175 * Pattern to match for session information (lttng list <session>)
176 */
177 private final static Pattern TRACE_SESSION_PATTERN = Pattern.compile("\\s*Tracing\\s+session\\s+(.*)\\:\\s+\\[(active|inactive)\\].*"); //$NON-NLS-1$
178 /**
179 * Pattern to match for session path information (lttng list <session>)
180 */
181 private final static Pattern TRACE_SESSION_PATH_PATTERN = Pattern.compile("\\s*Trace\\s+path\\:\\s+(.*)"); //$NON-NLS-1$
182 /**
183 * Pattern to match for kernel domain information (lttng list <session>)
184 */
185 private final static Pattern DOMAIN_KERNEL_PATTERN = Pattern.compile("=== Domain: Kernel ==="); //$NON-NLS-1$
186 /**
187 * Pattern to match for ust domain information (lttng list <session>)
188 */
189 private final static Pattern DOMAIN_UST_GLOBAL_PATTERN = Pattern.compile("=== Domain: UST global ==="); //$NON-NLS-1$
190 /**
191 * Pattern to match for channels section (lttng list <session>)
192 */
193 private final static Pattern CHANNELS_SECTION_PATTERN = Pattern.compile("\\s*Channels\\:"); //$NON-NLS-1$
194 /**
195 * Pattern to match for channel information (lttng list <session>)
196 */
197 private final static Pattern CHANNEL_PATTERN = Pattern.compile("\\s*-\\s+(.*)\\:\\s+\\[(enabled|disabled)\\]"); //$NON-NLS-1$
198 /**
199 * Pattern to match for events section information (lttng list <session>)
200 */
201 private final static Pattern EVENT_SECTION_PATTERN = Pattern.compile("\\s*Events\\:"); //$NON-NLS-1$
202 /**
203 * Pattern to match for event information (no enabled events) (lttng list <session>)
204 */
205 // private final static String EVENT_NONE_PATTERN = "\\s+None"; //$NON-NLS-1$
206 /**
207 * Pattern to match for event information (lttng list <session>)
208 */
209 private final static Pattern EVENT_PATTERN = Pattern.compile("\\s+(.*)\\s+\\(loglevel:\\s+(.*)\\s+\\(\\d*\\)\\)\\s+\\(type:\\s+(.*)\\)\\s+\\[(enabled|disabled)\\].*"); //$NON-NLS-1$
210 /**
211 * Pattern to match a wildcarded event information (lttng list <session>)
212 */
213 private final static Pattern WILDCARD_EVENT_PATTERN = Pattern.compile("\\s+(.*)\\s+\\(type:\\s+(.*)\\)\\s+\\[(enabled|disabled)\\].*"); //$NON-NLS-1$
214 /**
215 * Pattern to match for channel (overwite mode) information (lttng list
216 * <session>)
217 */
218 private final static Pattern OVERWRITE_MODE_ATTRIBUTE = Pattern.compile("\\s+overwrite\\s+mode\\:.*"); //$NON-NLS-1$
219 /**
220 * Pattern to match indicating false for overwrite mode
221 */
222 private final static String OVERWRITE_MODE_ATTRIBUTE_FALSE = "0"; //$NON-NLS-1$
223 /**
224 * Pattern to match for channel (sub-buffer size) information (lttng list
225 * <session>)
226 */
227 private final static Pattern SUBBUFFER_SIZE_ATTRIBUTE = Pattern.compile("\\s+subbufers\\s+size\\:.*"); //$NON-NLS-1$
228 /**
229 * Pattern to match for channel (number of sub-buffers) information (lttng
230 * list <session>)
231 */
232 private final static Pattern NUM_SUBBUFFERS_ATTRIBUTE = Pattern.compile("\\s+number\\s+of\\s+subbufers\\:.*"); //$NON-NLS-1$
233 /**
234 * Pattern to match for channel (switch timer) information (lttng list
235 * <session>)
236 */
237 private final static Pattern SWITCH_TIMER_ATTRIBUTE = Pattern.compile("\\s+switch\\s+timer\\s+interval\\:.*"); //$NON-NLS-1$
238 /**
239 * Pattern to match for channel (read timer) information (lttng list
240 * <session>)
241 */
242 private final static Pattern READ_TIMER_ATTRIBUTE = Pattern.compile("\\s+read\\s+timer\\s+interval\\:.*"); //$NON-NLS-1$
243 /**
244 * Pattern to match for channel (output type) information (lttng list
245 * <session>)
246 */
247 private final static Pattern OUTPUT_ATTRIBUTE = Pattern.compile("\\s+output\\:.*"); //$NON-NLS-1$
248 /**
249 * Pattern to match for provider information (lttng list -k/-u)
250 */
251 private final static Pattern PROVIDER_EVENT_PATTERN = Pattern.compile("\\s*(.*)\\s+\\(loglevel:\\s+(.*)\\s+\\(\\d*\\)\\)\\s+\\(type:\\s+(.*)\\)"); //$NON-NLS-1$
252 /**
253 * Pattern to match for UST provider information (lttng list -u)
254 */
255 private final static Pattern UST_PROVIDER_PATTERN = Pattern.compile("\\s*PID\\:\\s+(\\d+)\\s+-\\s+Name\\:\\s+(.*)"); //$NON-NLS-1$
256 /**
257 * Pattern to match for session information (lttng create <session name>)
258 */
259 private final static Pattern CREATE_SESSION_NAME_PATTERN = Pattern.compile("\\s*Session\\s+(.*)\\s+created\\."); //$NON-NLS-1$
260 /**
261 * Pattern to match for session path information (lttng create <session name>)
262 */
263 private final static Pattern CREATE_SESSION_PATH_PATTERN = Pattern.compile("\\s*Traces\\s+will\\s+be\\s+written\\s+in\\s+(.*).*"); //$NON-NLS-1$
264 /**
265 * Pattern to match for session command output for "session name not found".
266 */
267 private final static Pattern SESSION_NOT_FOUND_ERROR_PATTERN = Pattern.compile("\\s*Error:\\s+Session\\s+name\\s+not\\s+found"); //$NON-NLS-1$
268
269 // ------------------------------------------------------------------------
270 // Attributes
271 // ------------------------------------------------------------------------
272 /**
273 * The command shell implementation
274 */
275 private ICommandShell fCommandShell = null;
276
277 // ------------------------------------------------------------------------
278 // Constructors
279 // ------------------------------------------------------------------------
280
281 /**
282 * Constructor
283 *
284 * @param shell
285 * - the command shell implementation to use
286 */
287 public LTTngControlService(ICommandShell shell) {
288 fCommandShell = shell;
289 }
290
291 // ------------------------------------------------------------------------
292 // Operations
293 // ------------------------------------------------------------------------
294
295 /*
296 * (non-Javadoc)
297 *
298 * @see
299 * org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService
300 * #getSessionNames(org.eclipse.core.runtime.IProgressMonitor)
301 */
302 @Override
303 public String[] getSessionNames(IProgressMonitor monitor) throws ExecutionException {
304
305 String command = COMMAND_LIST;
306 ICommandResult result = fCommandShell.executeCommand(command, monitor);
307
308 if (isError(result)) {
309 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
310 }
311
312 // Output:
313 // Available tracing sessions:
314 // 1) mysession1 (/home/user/lttng-traces/mysession1-20120123-083928)
315 // [inactive]
316 // 2) mysession (/home/user/lttng-traces/mysession-20120123-083318)
317 // [inactive]
318 //
319 // Use lttng list <session_name> for more details
320
321 ArrayList<String> retArray = new ArrayList<String>();
322 int index = 0;
323 while (index < result.getOutput().length) {
324 String line = result.getOutput()[index];
325 Matcher matcher = SESSION_PATTERN.matcher(line);
326 if (matcher.matches()) {
327 retArray.add(matcher.group(2).trim());
328 }
329 index++;
330 }
331 return retArray.toArray(new String[retArray.size()]);
332 }
333
334 /*
335 * (non-Javadoc)
336 *
337 * @see
338 * org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService
339 * #getSession(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
340 */
341 @Override
342 public ISessionInfo getSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
343 String command = COMMAND_LIST + sessionName;
344 ICommandResult result = fCommandShell.executeCommand(command, monitor);
345
346 if (isError(result)) {
347 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
348 }
349
350 int index = 0;
351
352 // Output:
353 // Tracing session mysession2: [inactive]
354 // Trace path: /home/eedbhu/lttng-traces/mysession2-20120123-110330
355 ISessionInfo sessionInfo = new SessionInfo(sessionName);
356
357 while (index < result.getOutput().length) {
358 // Tracing session mysession2: [inactive]
359 // Trace path: /home/eedbhu/lttng-traces/mysession2-20120123-110330
360 //
361 // === Domain: Kernel ===
362 //
363 String line = result.getOutput()[index];
364 Matcher matcher = TRACE_SESSION_PATTERN.matcher(line);
365 if (matcher.matches()) {
366 sessionInfo.setSessionState(matcher.group(2));
367 index++;
368 continue;
369 }
370
371 matcher = TRACE_SESSION_PATH_PATTERN.matcher(line);
372 if (matcher.matches()) {
373 sessionInfo.setSessionPath(matcher.group(1).trim());
374 index++;
375 continue;
376 }
377
378 matcher = DOMAIN_KERNEL_PATTERN.matcher(line);
379 if (matcher.matches()) {
380 // Create Domain
381 IDomainInfo domainInfo = new DomainInfo(Messages.TraceControl_KernelDomainDisplayName);
382 sessionInfo.addDomain(domainInfo);
383
384 // in domain kernel
385 ArrayList<IChannelInfo> channels = new ArrayList<IChannelInfo>();
386 index = parseDomain(result.getOutput(), index, channels);
387
388 // set channels
389 domainInfo.setChannels(channels);
390
391 // set kernel flag
392 domainInfo.setIsKernel(true);
393 continue;
394 }
395
396 matcher = DOMAIN_UST_GLOBAL_PATTERN.matcher(line);
397 if (matcher.matches()) {
398 IDomainInfo domainInfo = new DomainInfo(Messages.TraceControl_UstGlobalDomainDisplayName);
399 sessionInfo.addDomain(domainInfo);
400
401 // in domain UST
402 ArrayList<IChannelInfo> channels = new ArrayList<IChannelInfo>();
403 index = parseDomain(result.getOutput(), index, channels);
404
405 // set channels
406 domainInfo.setChannels(channels);
407
408 // set kernel flag
409 domainInfo.setIsKernel(false);
410 continue;
411 }
412 index++;
413 }
414 return sessionInfo;
415 }
416
417 /*
418 * (non-Javadoc)
419 *
420 * @see
421 * org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService
422 * #getKernelProvider(org.eclipse.core.runtime.IProgressMonitor)
423 */
424 @Override
425 public List<IBaseEventInfo> getKernelProvider(IProgressMonitor monitor) throws ExecutionException {
426 String command = COMMAND_LIST_KERNEL;
427 ICommandResult result = fCommandShell.executeCommand(command, monitor);
428 if (isError(result)) {
429 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
430 }
431
432 // Kernel events:
433 // -------------
434 // sched_kthread_stop (type: tracepoint)
435 List<IBaseEventInfo> events = new ArrayList<IBaseEventInfo>();
436 getProviderEventInfo(result.getOutput(), 0, events);
437 return events;
438 }
439
440 /*
441 * (non-Javadoc)
442 *
443 * @see
444 * org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService
445 * #getUstProvider()
446 */
447 @Override
448 public List<IUstProviderInfo> getUstProvider() throws ExecutionException {
449 return getUstProvider(new NullProgressMonitor());
450 }
451
452 /*
453 * (non-Javadoc)
454 *
455 * @see
456 * org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService
457 * #getUstProvider(org.eclipse.core.runtime.IProgressMonitor)
458 */
459 @Override
460 public List<IUstProviderInfo> getUstProvider(IProgressMonitor monitor) throws ExecutionException {
461 String command = COMMAND_LIST_UST;
462 ICommandResult result = fCommandShell.executeCommand(command, monitor);
463
464 if (isError(result)) {
465 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
466 }
467
468 // UST events:
469 // -------------
470 //
471 // PID: 3635 - Name:
472 // /home/user/git/lttng-ust/tests/hello.cxx/.libs/lt-hello
473 // ust_tests_hello:tptest_sighandler (loglevel: TRACE_EMERG0) (type:
474 // tracepoint)
475 // ust_tests_hello:tptest (loglevel: TRACE_EMERG0) (type: tracepoint)
476 //
477 // PID: 6459 - Name:
478 // /home/user/git/lttng-ust/tests/hello.cxx/.libs/lt-hello
479 // ust_tests_hello:tptest_sighandler (loglevel: TRACE_EMERG0) (type:
480 // tracepoint)
481 // ust_tests_hello:tptest (loglevel: TRACE_EMERG0) (type: tracepoint)
482
483 List<IUstProviderInfo> allProviders = new ArrayList<IUstProviderInfo>();
484 IUstProviderInfo provider = null;
485
486 int index = 0;
487 while (index < result.getOutput().length) {
488 String line = result.getOutput()[index];
489 Matcher matcher = UST_PROVIDER_PATTERN.matcher(line);
490 if (matcher.matches()) {
491
492 provider = new UstProviderInfo(matcher.group(2).trim());
493 provider.setPid(Integer.valueOf(matcher.group(1).trim()));
494 List<IBaseEventInfo> events = new ArrayList<IBaseEventInfo>();
495 index = getProviderEventInfo(result.getOutput(), ++index, events);
496 provider.setEvents(events);
497 allProviders.add(provider);
498
499 } else {
500 index++;
501 }
502
503 }
504 return allProviders;
505 }
506
507 /*
508 * (non-Javadoc)
509 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#createSession(java.lang.String, java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
510 */
511 @Override
512 public ISessionInfo createSession(String sessionName, String sessionPath, IProgressMonitor monitor) throws ExecutionException {
513
514 String newName = formatParameter(sessionName);
515 String newPath = formatParameter(sessionPath);
516
517 String command = COMMAND_CREATE_SESSION + newName;
518 if (newPath != null && !"".equals(newPath)) { //$NON-NLS-1$
519 command += " -o " + newPath; //$NON-NLS-1$
520 }
521
522 ICommandResult result = fCommandShell.executeCommand(command, monitor);
523
524 if (isError(result)) {
525 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
526 }
527 //Session myssession2 created.
528 //Traces will be written in /home/user/lttng-traces/myssession2-20120209-095418
529 String[] output = result.getOutput();
530
531 // Get and verify session name
532 Matcher matcher = CREATE_SESSION_NAME_PATTERN.matcher(output[0]);
533 String name = null;
534
535 if (matcher.matches()) {
536 name = String.valueOf(matcher.group(1).trim());
537 } else {
538 // Output format not expected
539 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
540 Messages.TraceControl_UnexpectedCommnadOutputFormat + ":\n" + //$NON-NLS-1$
541 formatOutput(result.getOutput()));
542 }
543
544 if ((name == null) || (!name.equals(sessionName))) {
545 // Unexpected name returned
546 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
547 Messages.TraceControl_UnexpectedNameError + ": " + name); //$NON-NLS-1$
548 }
549
550 // Get and verify session path
551 matcher = CREATE_SESSION_PATH_PATTERN.matcher(output[1]);
552 String path = null;
553
554 if (matcher.matches()) {
555 path = String.valueOf(matcher.group(1).trim());
556 } else {
557 // Output format not expected
558 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
559 Messages.TraceControl_UnexpectedCommnadOutputFormat + ":\n" + //$NON-NLS-1$
560 formatOutput(result.getOutput()));
561 }
562
563 if ((path == null) || ((sessionPath != null) && (!path.contains(sessionPath)))) {
564 // Unexpected path
565 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
566 Messages.TraceControl_UnexpectedPathError + ": " + name); //$NON-NLS-1$
567 }
568
569 SessionInfo sessionInfo = new SessionInfo(name);
570 sessionInfo.setSessionPath(path);
571
572 return sessionInfo;
573 }
574
575 @Override
576 public void destroySession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
577 String newName = formatParameter(sessionName);
578 String command = COMMAND_DESTROY_SESSION + newName;
579
580 ICommandResult result = fCommandShell.executeCommand(command, monitor);
581 String[] output = result.getOutput();
582
583 if (isError(result)) {
584 // In case "session not found" treat it as success
585 if ((output == null) || (!SESSION_NOT_FOUND_ERROR_PATTERN.matcher(output[0]).matches())) {
586 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
587 }
588 }
589 //Session <sessionName> destroyed
590 }
591
592 /*
593 * (non-Javadoc)
594 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#startSession(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
595 */
596 @Override
597 public void startSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
598
599 String newSessionName = formatParameter(sessionName);
600
601 String command = COMMAND_START_SESSION + newSessionName;
602
603 ICommandResult result = fCommandShell.executeCommand(command, monitor);
604
605 if (isError(result)) {
606 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
607 }
608 //Session <sessionName> started
609 }
610
611 /*
612 * (non-Javadoc)
613 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#stopSession(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
614 */
615 @Override
616 public void stopSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
617 String newSessionName = formatParameter(sessionName);
618 String command = COMMAND_STOP_SESSION + newSessionName;
619
620 ICommandResult result = fCommandShell.executeCommand(command, monitor);
621
622 if (isError(result)) {
623 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
624 }
625 //Session <sessionName> stopped
626
627 }
628
629 /*
630 * (non-Javadoc)
631 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#enableChannel(java.lang.String, java.util.List, boolean, org.eclipse.linuxtools.lttng.ui.views.control.model.IChannelInfo, org.eclipse.core.runtime.IProgressMonitor)
632 */
633 @Override
634 public void enableChannels(String sessionName, List<String> channelNames, boolean isKernel, IChannelInfo info, IProgressMonitor monitor) throws ExecutionException {
635
636 // no channels to enable
637 if (channelNames.size() == 0) {
638 return;
639 }
640
641 String newSessionName = formatParameter(sessionName);
642
643 StringBuffer command = new StringBuffer(COMMAND_ENABLE_CHANNEL);
644
645 for (Iterator<String> iterator = channelNames.iterator(); iterator.hasNext();) {
646 String channel = (String) iterator.next();
647 command.append(channel);
648 if (iterator.hasNext()) {
649 command.append(","); //$NON-NLS-1$
650 }
651 }
652
653 if (isKernel) {
654 command.append(OPTION_KERNEL);
655 } else {
656 command.append(OPTION_UST);
657 }
658
659 command.append(OPTION_SESSION);
660 command.append(newSessionName);
661
662 if (info != null) {
663 // --discard Discard event when buffers are full (default)
664
665 // --overwrite Flight recorder mode
666 if (info.isOverwriteMode()) {
667 command.append(OPTION_OVERWRITE);
668 }
669 // --subbuf-size SIZE Subbuffer size in bytes
670 // (default: 4096, kernel default: 262144)
671 command.append(OPTION_SUB_BUFFER_SIZE);
672 command.append(String.valueOf(info.getSubBufferSize()));
673
674 // --num-subbuf NUM Number of subbufers
675 // (default: 8, kernel default: 4)
676 command.append(OPTION_NUM_SUB_BUFFERS);
677 command.append(String.valueOf(info.getNumberOfSubBuffers()));
678
679 // --switch-timer USEC Switch timer interval in usec (default: 0)
680 command.append(OPTION_SWITCH_TIMER);
681 command.append(String.valueOf(info.getSwitchTimer()));
682
683 // --read-timer USEC Read timer interval in usec (default: 200)
684 command.append(OPTION_READ_TIMER);
685 command.append(String.valueOf(info.getReadTimer()));
686 }
687
688 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
689
690 if (isError(result)) {
691 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
692 }
693 }
694
695 /*
696 * (non-Javadoc)
697 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#disableChannel(java.lang.String, java.util.List, org.eclipse.core.runtime.IProgressMonitor)
698 */
699 @Override
700 public void disableChannels(String sessionName, List<String> channelNames, boolean isKernel, IProgressMonitor monitor) throws ExecutionException {
701
702 // no channels to enable
703 if (channelNames.size() == 0) {
704 return;
705 }
706
707 String newSessionName = formatParameter(sessionName);
708
709 StringBuffer command = new StringBuffer(COMMAND_DISABLE_CHANNEL);
710
711 for (Iterator<String> iterator = channelNames.iterator(); iterator.hasNext();) {
712 String channel = (String) iterator.next();
713 command.append(channel);
714 if (iterator.hasNext()) {
715 command.append(","); //$NON-NLS-1$
716 }
717 }
718
719 if (isKernel) {
720 command.append(OPTION_KERNEL);
721 } else {
722 command.append(OPTION_UST);
723 }
724
725 command.append(OPTION_SESSION);
726 command.append(newSessionName);
727
728 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
729
730 if (isError(result)) {
731 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
732 }
733 }
734
735 /*
736 * (non-Javadoc)
737 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#enableEvent(java.lang.String, java.lang.String, java.util.List, boolean, org.eclipse.core.runtime.IProgressMonitor)
738 */
739 @Override
740 public void enableEvents(String sessionName, String channelName, List<String> eventNames, boolean isKernel, IProgressMonitor monitor) throws ExecutionException {
741
742 String newSessionName = formatParameter(sessionName);
743
744 StringBuffer command = new StringBuffer(COMMAND_ENABLE_EVENT);
745
746 if (eventNames == null) {
747 command.append(OPTION_ALL);
748 } else {
749 // no events to enable
750 if (eventNames.size() == 0) {
751 return;
752 }
753
754 for (Iterator<String> iterator = eventNames.iterator(); iterator.hasNext();) {
755 String event = (String) iterator.next();
756 command.append(event);
757 if (iterator.hasNext()) {
758 command.append(","); //$NON-NLS-1$
759 }
760 }
761 }
762
763 if (isKernel) {
764 command.append(OPTION_KERNEL);
765 } else {
766 command.append(OPTION_UST);
767 }
768
769 command.append(OPTION_SESSION);
770 command.append(newSessionName);
771
772 if (channelName != null) {
773 command.append(OPTION_CHANNEL);
774 command.append(channelName);
775 }
776
777 command.append(OPTION_TRACEPOINT);
778
779 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
780
781 if (isError(result)) {
782 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
783 }
784 }
785
786 /*
787 * (non-Javadoc)
788 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#enableSyscalls(java.lang.String, java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
789 */
790 @Override
791 public void enableSyscalls(String sessionName, String channelName, IProgressMonitor monitor) throws ExecutionException {
792 String newSessionName = formatParameter(sessionName);
793
794 StringBuffer command = new StringBuffer(COMMAND_ENABLE_EVENT);
795
796 command.append(OPTION_ALL);
797 command.append(OPTION_KERNEL);
798
799 command.append(OPTION_SESSION);
800 command.append(newSessionName);
801
802 if (channelName != null) {
803 command.append(OPTION_CHANNEL);
804 command.append(channelName);
805 }
806
807 command.append(OPTION_SYSCALL);
808
809 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
810
811 if (isError(result)) {
812 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
813 }
814 }
815
816 /*
817 * (non-Javadoc)
818 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#enableProbe(java.lang.String, java.lang.String, java.lang.String, java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
819 */
820 @Override
821 public void enableProbe(String sessionName, String channelName, String eventName, String probe, IProgressMonitor monitor) throws ExecutionException {
822 String newSessionName = formatParameter(sessionName);
823
824 StringBuffer command = new StringBuffer(COMMAND_ENABLE_EVENT);
825
826 command.append(eventName);
827 command.append(OPTION_KERNEL);
828
829 command.append(OPTION_SESSION);
830 command.append(newSessionName);
831
832 if (channelName != null) {
833 command.append(OPTION_CHANNEL);
834 command.append(channelName);
835 }
836
837 command.append(OPTION_PROBE);
838 command.append(probe);
839
840 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
841
842 if (isError(result)) {
843 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
844 }
845 }
846
847 /*
848 * (non-Javadoc)
849 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#enableFunctionProbe(java.lang.String, java.lang.String, java.lang.String, java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
850 */
851 @Override
852 public void enableFunctionProbe(String sessionName, String channelName, String eventName, String probe, IProgressMonitor monitor) throws ExecutionException {
853 String newSessionName = formatParameter(sessionName);
854
855 StringBuffer command = new StringBuffer(COMMAND_ENABLE_EVENT);
856
857 command.append(eventName);
858 command.append(OPTION_KERNEL);
859
860 command.append(OPTION_SESSION);
861 command.append(newSessionName);
862
863 if (channelName != null) {
864 command.append(OPTION_CHANNEL);
865 command.append(channelName);
866 }
867
868 command.append(OPTION_FUNCTION_PROBE);
869 command.append(probe);
870
871 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
872
873 if (isError(result)) {
874 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
875 }
876 }
877
878 /*
879 * (non-Javadoc)
880 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#enableLogLevel(java.lang.String, java.lang.String, java.lang.String, org.eclipse.linuxtools.lttng.ui.views.control.model.LogLevelType, org.eclipse.linuxtools.lttng.ui.views.control.model.TraceLogLevel, org.eclipse.core.runtime.IProgressMonitor)
881 */
882 @Override
883 public void enableLogLevel(String sessionName, String channelName, String eventName, LogLevelType logLevelType, TraceLogLevel level, IProgressMonitor monitor) throws ExecutionException {
884 String newSessionName = formatParameter(sessionName);
885
886 StringBuffer command = new StringBuffer(COMMAND_ENABLE_EVENT);
887
888 command.append(eventName);
889 command.append(OPTION_UST);
890
891 command.append(OPTION_SESSION);
892 command.append(newSessionName);
893
894 if (channelName != null) {
895 command.append(OPTION_CHANNEL);
896 command.append(channelName);
897 }
898
899 if (logLevelType == LogLevelType.LOGLEVEL) {
900 command.append(OPTION_LOGLEVEL);
901 } else if (logLevelType == LogLevelType.LOGLEVEL_ONLY) {
902 command.append(OPTION_LOGLEVEL_ONLY);
903
904 } else {
905 return;
906 }
907 command.append(level.getInName());
908
909 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
910
911 if (isError(result)) {
912 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
913 }
914 }
915
916 /*
917 * (non-Javadoc)
918 * @see org.eclipse.linuxtools.lttng.ui.views.control.service.ILttngControlService#disableEvent(java.lang.String, java.lang.String, java.util.List, boolean, org.eclipse.core.runtime.IProgressMonitor)
919 */
920 @Override
921 public void disableEvent(String sessionName, String channelName, List<String> eventNames, boolean isKernel, IProgressMonitor monitor) throws ExecutionException {
922 String newSessionName = formatParameter(sessionName);
923
924 StringBuffer command = new StringBuffer(COMMAND_DISABLE_EVENT);
925 if (eventNames == null) {
926 command.append(OPTION_ALL);
927 } else {
928 // no events to enable
929 if (eventNames.size() == 0) {
930 return;
931 }
932
933 for (Iterator<String> iterator = eventNames.iterator(); iterator.hasNext();) {
934 String event = (String) iterator.next();
935 command.append(event);
936 if (iterator.hasNext()) {
937 command.append(","); //$NON-NLS-1$
938 }
939 }
940 }
941
942 if (isKernel) {
943 command.append(OPTION_KERNEL);
944 } else {
945 command.append(OPTION_UST);
946 }
947
948 command.append(OPTION_SESSION);
949 command.append(newSessionName);
950
951 if (channelName != null) {
952 command.append(OPTION_CHANNEL);
953 command.append(channelName);
954 }
955
956 ICommandResult result = fCommandShell.executeCommand(command.toString(), monitor);
957
958 if (isError(result)) {
959 throw new ExecutionException(Messages.TraceControl_CommandError + " " + command + "\n" + formatOutput(result.getOutput())); //$NON-NLS-1$ //$NON-NLS-2$
960 }
961 }
962
963 // ------------------------------------------------------------------------
964 // Helper methods
965 // ------------------------------------------------------------------------
966 /**
967 * Checks if command result is an error result.
968 *
969 * @param result
970 * - the command result to check
971 * @return true if error else false
972 */
973 private boolean isError(ICommandResult result) {
974 if ((result.getResult()) != 0 || (result.getOutput().length < 1 || ERROR_PATTERN.matcher(result.getOutput()[0]).matches())) {
975 return true;
976 }
977 return false;
978 }
979
980 /**
981 * Formats the output string as single string.
982 *
983 * @param output
984 * - output array
985 * @return - the formatted output
986 */
987 private String formatOutput(String[] output) {
988 if (output == null || output.length == 0) {
989 return ""; //$NON-NLS-1$
990 }
991
992 StringBuffer ret = new StringBuffer();
993 for (int i = 0; i < output.length; i++) {
994 ret.append(output[i] + "\n"); //$NON-NLS-1$
995 }
996 return ret.toString();
997 }
998
999 /**
1000 * Parses the domain information.
1001 *
1002 * @param output
1003 * - a command output array
1004 * @param currentIndex
1005 * - current index in command output array
1006 * @param channels
1007 * - list for returning channel information
1008 * @return the new current index in command output array
1009 */
1010 private int parseDomain(String[] output, int currentIndex, List<IChannelInfo> channels) {
1011 int index = currentIndex;
1012
1013 // Channels:
1014 // -------------
1015 // - channnel1: [enabled]
1016 //
1017 // Attributes:
1018 // overwrite mode: 0
1019 // subbufers size: 262144
1020 // number of subbufers: 4
1021 // switch timer interval: 0
1022 // read timer interval: 200
1023 // output: splice()
1024
1025 while (index < output.length) {
1026 String line = output[index];
1027
1028 Matcher outerMatcher = CHANNELS_SECTION_PATTERN.matcher(line);
1029 if (outerMatcher.matches()) {
1030 IChannelInfo channelInfo = null;
1031 while (index < output.length) {
1032 String subLine = output[index];
1033
1034 Matcher innerMatcher = CHANNEL_PATTERN.matcher(subLine);
1035 if (innerMatcher.matches()) {
1036 channelInfo = new ChannelInfo(""); //$NON-NLS-1$
1037 // get channel name
1038 channelInfo.setName(innerMatcher.group(1));
1039
1040 // get channel enablement
1041 channelInfo.setState(innerMatcher.group(2));
1042
1043 // add channel
1044 channels.add(channelInfo);
1045
1046 } else if (OVERWRITE_MODE_ATTRIBUTE.matcher(subLine).matches()) {
1047 String value = getAttributeValue(subLine);
1048 channelInfo.setOverwriteMode(!OVERWRITE_MODE_ATTRIBUTE_FALSE.equals(value));
1049 } else if (SUBBUFFER_SIZE_ATTRIBUTE.matcher(subLine).matches()) {
1050 channelInfo.setSubBufferSize(Long.valueOf(getAttributeValue(subLine)));
1051
1052 } else if (NUM_SUBBUFFERS_ATTRIBUTE.matcher(subLine).matches()) {
1053 channelInfo.setNumberOfSubBuffers(Integer.valueOf(getAttributeValue(subLine)));
1054
1055 } else if (SWITCH_TIMER_ATTRIBUTE.matcher(subLine).matches()) {
1056 channelInfo.setSwitchTimer(Long.valueOf(getAttributeValue(subLine)));
1057
1058 } else if (READ_TIMER_ATTRIBUTE.matcher(subLine).matches()) {
1059 channelInfo.setReadTimer(Long.valueOf(getAttributeValue(subLine)));
1060
1061 } else if (OUTPUT_ATTRIBUTE.matcher(subLine).matches()) {
1062 channelInfo.setOutputType(getAttributeValue(subLine));
1063
1064 } else if (EVENT_SECTION_PATTERN.matcher(subLine).matches()) {
1065 List<IEventInfo> events = new ArrayList<IEventInfo>();
1066 index = parseEvents(output, index, events);
1067 channelInfo.setEvents(events);
1068 // we want to stay at the current index to be able to
1069 // exit the domain
1070 continue;
1071 } else if (DOMAIN_KERNEL_PATTERN.matcher(subLine).matches()) {
1072 return index;
1073
1074 } else if (DOMAIN_UST_GLOBAL_PATTERN.matcher(subLine).matches()) {
1075 return index;
1076 }
1077 index++;
1078 }
1079 }
1080 index++;
1081 }
1082 return index;
1083 }
1084
1085 /**
1086 * Parses the event information within a domain.
1087 *
1088 * @param output
1089 * - a command output array
1090 * @param currentIndex
1091 * - current index in command output array
1092 * @param events
1093 * - list for returning event information
1094 * @return the new current index in command output array
1095 */
1096 private int parseEvents(String[] output, int currentIndex, List<IEventInfo> events) {
1097 int index = currentIndex;
1098
1099 while (index < output.length) {
1100 String line = output[index];
1101 if (CHANNEL_PATTERN.matcher(line).matches()) {
1102 // end of channel
1103 return index;
1104 } else if (DOMAIN_KERNEL_PATTERN.matcher(line).matches()) {
1105 // end of domain
1106 return index;
1107 } else if (DOMAIN_UST_GLOBAL_PATTERN.matcher(line).matches()) {
1108 // end of domain
1109 return index;
1110 }
1111
1112 Matcher matcher = EVENT_PATTERN.matcher(line);
1113 Matcher matcher2 = WILDCARD_EVENT_PATTERN.matcher(line);
1114
1115 if (matcher.matches()) {
1116 IEventInfo eventInfo = new EventInfo(matcher.group(1).trim());
1117 eventInfo.setLogLevel(matcher.group(2).trim());
1118 eventInfo.setEventType(matcher.group(3).trim());
1119 eventInfo.setState(matcher.group(4));
1120 events.add(eventInfo);
1121 } else if (matcher2.matches()) {
1122 IEventInfo eventInfo = new EventInfo(matcher2.group(1).trim());
1123 eventInfo.setLogLevel(TraceLogLevel.LEVEL_UNKNOWN);
1124 eventInfo.setEventType(matcher2.group(2).trim());
1125 eventInfo.setState(matcher2.group(3));
1126 events.add(eventInfo);
1127 }
1128 // else if (line.matches(EVENT_NONE_PATTERN)) {
1129 // do nothing
1130 // } else
1131 index++;
1132 }
1133
1134 return index;
1135 }
1136
1137 /**
1138 * Parses a line with attributes: <attribute Name>: <attribute value>
1139 *
1140 * @param line
1141 * - attribute line to parse
1142 * @return the attribute value as string
1143 */
1144 private String getAttributeValue(String line) {
1145 String[] temp = line.split("\\: "); //$NON-NLS-1$
1146 return temp[1];
1147 }
1148
1149 /**
1150 * Parses the event information within a provider.
1151 *
1152 * @param output
1153 * - a command output array
1154 * @param currentIndex
1155 * - current index in command output array
1156 * @param events
1157 * - list for returning event information
1158 * @return the new current index in command output array
1159 */
1160 private int getProviderEventInfo(String[] output, int currentIndex, List<IBaseEventInfo> events) {
1161 int index = currentIndex;
1162 while (index < output.length) {
1163 String line = output[index];
1164 Matcher matcher = PROVIDER_EVENT_PATTERN.matcher(line);
1165 if (matcher.matches()) {
1166 // sched_kthread_stop (loglevel: TRACE_EMERG0) (type:
1167 // tracepoint)
1168 IBaseEventInfo eventInfo = new BaseEventInfo(matcher.group(1).trim());
1169 eventInfo.setLogLevel(matcher.group(2).trim());
1170 eventInfo.setEventType(matcher.group(3).trim());
1171 events.add(eventInfo);
1172 } else if (UST_PROVIDER_PATTERN.matcher(line).matches()) {
1173 return index;
1174 }
1175 index++;
1176 }
1177 return index;
1178 }
1179
1180 /**
1181 * Formats a command parameter for the command execution i.e. adds quotes
1182 * at the beginning and end if necessary.
1183 * @param parameter - parameter to format
1184 * @return formated parameter
1185 */
1186 private String formatParameter(String parameter) {
1187 if (parameter != null) {
1188 String newString = String.valueOf(parameter);
1189
1190 if (parameter.contains(" ")) { //$NON-NLS-1$
1191 newString = "\"" + newString + "\""; //$NON-NLS-1$ //$NON-NLS-2$
1192 }
1193 return newString;
1194 }
1195 return null;
1196 }
1197
1198 }
This page took 0.057574 seconds and 5 git commands to generate.