/*******************************************************************************
* Copyright (c) 2006, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - Jeff Briggs, Henry Hughes, Ryan Morse
*******************************************************************************/
package org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.tparsers;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.Messages;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.nodedata.ProbeNodeData;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.nodedata.ProbevarNodeData;
import org.eclipse.linuxtools.systemtap.structures.TreeDefinitionNode;
import org.eclipse.linuxtools.systemtap.structures.TreeNode;
/**
* Runs stap -vp1 & stap -L in order to get all of the probes
* that are defined in the tapsets. Builds probeAlias trees
* with the values obtained from the tapsets.
*
* @author Ryan Morse
* @since 2.0
*/
public final class ProbeParser extends TreeTapsetParser {
public static final String PROBE_REGEX = "(?s)(?<!\\w)probe\\s+{0}\\s*\\+?="; //$NON-NLS-1$
private static final String TAPSET_PROBE_REGEX = "probe {0} \\+?="; //$NON-NLS-1$
private static final String PROBE_FORM_CHECK_REGEX = "\\w+((\\(\\w+\\))?(\\.\\w+)?)*( \\$?\\w+:\\w+)*"; //$NON-NLS-1$
private static final Pattern PROBE_GROUP_PATTERN = Pattern.compile("[^\\.\\(]+"); //$NON-NLS-1$
private static ProbeParser parser = null;
public static ProbeParser getInstance(){
if (parser != null) {
return parser;
}
parser = new ProbeParser();
return parser;
}
private ProbeParser() {
super(Messages.ProbeParser_name);
}
/**
* @param tree To be valid, the first-level children of this tree must
* be two nodes respectively named "Static Probes" and "Probe Alias".
*/
@Override
protected boolean isValidTree(TreeNode tree) {
return super.isValidTree(tree) &&
tree.getChildByName(Messages.ProbeParser_staticProbes) != null
&& tree.getChildByName(Messages.ProbeParser_aliasProbes) != null;
}
/**
* Runs stap to collect all available tapset probes.
* ProbeTree organized as:
* Root->Named Groups->ProbePoints->Variables
*/
@Override
protected int runAction(IProgressMonitor monitor) {
int result = addStaticProbes(monitor);
if (result == IStatus.OK) {
result = addProbeAliases(monitor);
}
return result;
}
/**
* Runs stap to obtain a log of all static probes, and populate the probe tree with them.
* @return An {@link IStatus} severity level for the result of the operation.
*/
private int addStaticProbes(IProgressMonitor monitor) {
TreeNode statics = new TreeNode(Messages.ProbeParser_staticProbes, false);
tree.add(statics);
if (monitor.isCanceled()) {
return IStatus.CANCEL;
}
String probeDump = runStap(new String[]{"--dump-probe-types"}, null, false); //$NON-NLS-1$
int result = verifyRunResult(probeDump);
if (result != IStatus.OK) {
return result;
}
if (!doQuickErrorCheck(probeDump)) {
return IStatus.ERROR;
}
boolean canceled = false;
try (Scanner st = new Scanner(probeDump)) {
TreeNode groupNode = null;
while (st.hasNextLine()) {
if (monitor.isCanceled()) {
canceled = true;
break;
}
String tokenString = st.nextLine();
groupNode = addOrFindProbeGroup(extractProbeGroupName(tokenString), groupNode, statics);
groupNode.add(makeStaticProbeNode(tokenString));
}
}
statics.sortTree();
return !canceled ? IStatus.OK : IStatus.CANCEL;
}
/**
* Runs stap to obtain a log of all probe aliases & their variables,
* and populate the probe tree with them.
* @return An {@link IStatus} severity level for the result of the operation.
*/
private int addProbeAliases(IProgressMonitor monitor) {
TreeNode statics = tree.getChildByName(Messages.ProbeParser_staticProbes);
if (statics == null) {
return IStatus.ERROR;
}
TreeNode aliases = new TreeNode(Messages.ProbeParser_aliasProbes, false);
tree.add(aliases);
if (monitor.isCanceled()) {
return IStatus.CANCEL;
}
String probeDump = runStap(new String[]{"-L"}, "**", false); //$NON-NLS-1$ //$NON-NLS-2$
int result = verifyRunResult(probeDump);
if (result != IStatus.OK) {
return result;
}
if (!doQuickErrorCheck(probeDump)) {
return IStatus.ERROR;
}
boolean canceled = false;
try (Scanner st = new Scanner(probeDump)) {
TreeNode groupNode = null;
while (st.hasNextLine()) {
if (monitor.isCanceled()) {
canceled = true;
break;
}
String tokenString = st.nextLine();
// If the token starts with '_' or '__' it is a private probe so
// skip it.
if (tokenString.startsWith("_")) { //$NON-NLS-1$
continue;
}
// Only add this group if it is not a static probe group
String groupName = extractProbeGroupName(tokenString);
if (statics.getChildByName(groupName) != null) {
continue;
}
groupNode = addSingleProbeAlias(tokenString, aliases, groupNode, groupName, null);
}
}
aliases.sortTree();
return !canceled ? IStatus.OK : IStatus.CANCEL;
}
/**
* Performs a quick check of validity in a probe dump.
* @param probeDump The output of a call to stap that prints a probe list.
* @return <code>false</code> if the output of the dump is invalid.
*/
private boolean doQuickErrorCheck(String probeDump) {
if (probeDump == null) {
return false;
}
// Check just the first probe printed
try (Scanner scanner = new Scanner(probeDump)) {
return Pattern.matches(PROBE_FORM_CHECK_REGEX, scanner.nextLine());
}
}
/**
* Adds a single probe alias to the collection.
* @param probeLine A line of probe information printed by a call to "stap -L".
* @param aliases The tree of probe aliases. The probe will be added to this tree.
* @param groupNode For optimization, pass an existing group node here, as it will be used if the
* probe belongs in it. Otherwise, or if <code>null</code> is passed, a new one will be created.
* @param groupName The name of the probe group, or <code>null</code> if it is unknown at the time
* this method is called.
* @param definition The path of the file in which this probe is defined, or <code>null</code> if it
* is unknown at the time this method is called.
*/
private TreeNode addSingleProbeAlias(String probeLine, TreeNode aliases, TreeNode groupNode,
String groupName, String definition) {
StringTokenizer probeTokenizer = new StringTokenizer(probeLine);
String probeName = probeTokenizer.nextToken();
TreeNode probeNode = makeProbeAliasNode(probeName,
definition == null ? findDefinitionOf(probeName) : definition);
groupNode = addOrFindProbeGroup(
groupName == null ? extractProbeGroupName(probeName) : groupName,
groupNode, aliases);
groupNode.add(probeNode);
addAllVarNodesToProbeNode(probeTokenizer, probeNode);
return groupNode;
}
/**
* Finds the appropriate parent group node for a probe alias to group probes by name.
* If it doesn't yet exist, create it and add it to the view's tree.
* @param groupName The name of the probe group.
* @param groupNode For optimization, pass an existing group node here, as it will be
* used if the probe belongs in it. Otherwise, or if <code>null</code> is passed, a new one will be created.
* @param category The parent tree node in which to put the group node.
* @return The found or created group node that will be the parent of the probe's entry item in the view.
*/
private TreeNode addOrFindProbeGroup(String groupName, TreeNode groupNode, TreeNode category) {
// If the current probe belongs to a group other than
// the most recent group. This should rarely be needed because the
// probe list is sorted... mostly.
if (groupNode == null || !groupNode.toString().equals(groupName)) {
groupNode = category.getChildByName(groupName);
}
// Create a new group and add it
if (groupNode == null) {
groupNode = new TreeNode(groupName, true);
category.add(groupNode);
}
return groupNode;
}
/**
* @return the name of the group a probe belongs to, based on the probe's name.
*/
private String extractProbeGroupName(String probeName) {
Matcher m = PROBE_GROUP_PATTERN.matcher(probeName);
return m.find() ? m.group() : probeName;
}
private TreeNode makeStaticProbeNode(String probeName) {
return new TreeNode(new ProbeNodeData(probeName), probeName, true);
}
private TreeNode makeProbeAliasNode(String probeName, String definition) {
return new TreeDefinitionNode(new ProbeNodeData(probeName), probeName, definition, true);
}
/**
* Searches the tapset content dump for the path of the file which defines the provided probe alias.
* @param probeName The alias of the probe to find the definition file of.
* @return The path of the probe's definition file, or <code>null</code> if a definition
* file can't be found (which is the case for static probes).
*/
private String findDefinitionOf(String probeName) {
String tapsetContents = SharedParser.getInstance().getTapsetContents();
if (tapsetContents == null) {
return null;
}
Matcher probeMatcher = Pattern.compile(MessageFormat.format(
TAPSET_PROBE_REGEX, Pattern.quote(probeName))).matcher(tapsetContents);
if (!probeMatcher.find()) {
return null;
}
int fileLocIndex = tapsetContents.substring(0, probeMatcher.start())
.lastIndexOf(SharedParser.TAG_FILE);
try (Scanner scanner = new Scanner(tapsetContents.substring(fileLocIndex))) {
return SharedParser.findFileNameInTag(scanner.nextLine());
}
}
/**
* Extracts the local variables from a (partially examined) probe alias token, and
* adds them as child tree entries of their parent probe.
*/
private void addAllVarNodesToProbeNode(StringTokenizer varTokenizer, TreeNode probeNode) {
StringBuilder prev = new StringBuilder(""); //$NON-NLS-1$
// the remaining tokens are variable names and variable types name:type.
while (varTokenizer.hasMoreTokens()) {
String token = varTokenizer.nextToken();
// Because some variable types contain spaces (var2:struct task_struct)
// the only way to know if we have the entire string representing a
// variable is if we reach the next token containing a ':' or we reach
// the end of the stream.
if (token.contains(":") && prev.length() > 0) { //$NON-NLS-1$
prev.setLength(prev.length() - 1); // Remove the trailing space.
addVarNodeToProbeNode(prev.toString(), probeNode);
prev.setLength(0);
}
prev.append(token + " "); //$NON-NLS-1$
}
// Add the last token if there is one
if (prev.length() > 0) {
prev.setLength(prev.length() - 1); // Remove the trailing space.
addVarNodeToProbeNode(prev.toString(), probeNode);
}
probeNode.sortLevel();
}
private void addVarNodeToProbeNode(String info, TreeNode probeNode) {
probeNode.add(new TreeNode(new ProbevarNodeData(info), info, false));
}
@Override
protected int delTapsets(String[] tapsets, IProgressMonitor monitor) {
TreeNode aliases = tree.getChildByName(Messages.ProbeParser_aliasProbes);
// Search through alias groups for probes whose definition files
// come from removed directories, and remove them from the group.
for (int i = 0; i < tapsets.length; i++) {
for (int g = 0, gn = aliases.getChildCount(); g < gn; g++) {
if (monitor.isCanceled()) {
return IStatus.CANCEL;
}
TreeNode group = aliases.getChildAt(g);
for (int p = 0, pn = group.getChildCount(); p < pn; p++) {
String definition = ((TreeDefinitionNode) group.getChildAt(p)).getDefinition();
if (definition != null && definition.startsWith(tapsets[i])) {
group.remove(p--);
pn--;
}
}
// If removing the only probe left in a probe group, remove the group.
if (group.getChildCount() == 0) {
aliases.remove(g--);
gn--;
}
}
}
return IStatus.OK;
}
@Override
protected int addTapsets(String tapsetContents, String[] additions, IProgressMonitor monitor) {
boolean canceled = false;
TreeNode aliases = tree.getChildByName(Messages.ProbeParser_aliasProbes);
Map<String, ArrayList<String>> fileToItemMap = new HashMap<>();
// Search tapset contents for all files provided by each added directory.
for (int i = 0; i < additions.length; i++) {
int firstTagIndex = 0;
while (true) {
// Get the contents of each file provided by the directory additions[i].
firstTagIndex = tapsetContents.indexOf(
SharedParser.makeFileTag(additions[i]), firstTagIndex);
if (firstTagIndex == -1) {
break;
}
int nextTagIndex = tapsetContents.indexOf(SharedParser.TAG_FILE, firstTagIndex + 1);
String fileContents = nextTagIndex != -1
? tapsetContents.substring(firstTagIndex, nextTagIndex)
: tapsetContents.substring(firstTagIndex);
String filename;
try (Scanner st = new Scanner(fileContents)) {
filename = SharedParser.findFileNameInTag(st.nextLine());
}
// Search file contents for the probes the file provides.
ArrayList<String> newItems = new ArrayList<>();
Matcher matcher = Pattern.compile(MessageFormat.format(
TAPSET_PROBE_REGEX, "(\\S+)")) //$NON-NLS-1$
.matcher(fileContents);
while (matcher.find()) {
newItems.add(matcher.group(1));
}
if (!newItems.isEmpty()) {
fileToItemMap.put(filename, newItems);
}
// Remove the contents of the file that was just examined from the total contents.
tapsetContents = tapsetContents.substring(0, firstTagIndex).concat(
tapsetContents.substring(firstTagIndex + fileContents.length()));
}
}
// Run stap on each discovered probe to obtain their variable information.
for (Map.Entry<String, ArrayList<String>> entry : fileToItemMap.entrySet()) {
for (String newitem : entry.getValue()) {
if (canceled || monitor.isCanceled()) {
canceled = true;
break;
}
addSingleProbeAlias(runStap(new String[]{"-L"}, newitem, false), //$NON-NLS-1$
aliases, null, null, entry.getKey());
}
}
aliases.sortTree();
return !canceled ? IStatus.OK : IStatus.CANCEL;
}
}