mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-177967 Trace native awt calls on OSX using dtrace
Implemented Tools->Internal Action->Profiler->DTrace lwawt action
This commit is contained in:
+441
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.internal.performance.profiler.dtrace;
|
||||
|
||||
import com.intellij.credentialStore.CredentialAttributes;
|
||||
import com.intellij.credentialStore.Credentials;
|
||||
import com.intellij.credentialStore.OneTimeString;
|
||||
import com.intellij.execution.CommandLineUtil;
|
||||
import com.intellij.execution.configurations.PtyCommandLine;
|
||||
import com.intellij.execution.process.*;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.passwordSafe.PasswordSafe;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.ui.popup.*;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.StreamUtil;
|
||||
import com.intellij.ui.components.JBScrollPane;
|
||||
import com.intellij.ui.popup.list.ListPopupImpl;
|
||||
import com.intellij.util.SystemProperties;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DTraceAction extends AnAction implements DumbAware {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.internal.performance.profiler.dtrace");
|
||||
|
||||
private static final String DTRACE_SUDO_KEY = "DTRACE_SUDO_KEY";
|
||||
|
||||
private static CredentialAttributes getSudoCredAttr() {
|
||||
return new CredentialAttributes(DTRACE_SUDO_KEY, SystemProperties.getUserName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static char[] getCredentials() {
|
||||
Credentials cred = PasswordSafe.getInstance().get(getSudoCredAttr());
|
||||
if (cred == null) return null;
|
||||
final OneTimeString password = cred.getPassword();
|
||||
return password != null ? password.toCharArray() : null;
|
||||
}
|
||||
|
||||
|
||||
private static void saveCredentials(char[] password) {
|
||||
PasswordSafe.getInstance().set(getSudoCredAttr(), new Credentials(SystemProperties.getUserName(), password), false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
if (SystemInfo.isMac) {
|
||||
(new DTraceWindow()).setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class DTraceWindow extends JDialog {
|
||||
|
||||
|
||||
private JTextArea outputArea;
|
||||
private JTextField pidField;
|
||||
private volatile boolean isRunning = false;
|
||||
private OSProcessHandler myProcessHandler;
|
||||
private OutputStream mySink;
|
||||
File tmpDir;
|
||||
String lwawtScr;
|
||||
Process p;
|
||||
volatile boolean outputData;
|
||||
char[] myPasswd;
|
||||
boolean rememberPasswd = false;
|
||||
|
||||
public DTraceWindow() {
|
||||
super((Frame)null);
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setLayout(new GridBagLayout());
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.weightx = 0.1;
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
|
||||
pidField = new JTextField(4);
|
||||
pidField.setText(ApplicationManager.getApplicationPid());
|
||||
add(pidField, c);
|
||||
|
||||
JPanel buttonPane = new JPanel(new GridLayout(1, 2));
|
||||
|
||||
final JButton select = new JButton(AllIcons.Actions.FindPlain);
|
||||
select.addActionListener(e -> selectProcess());
|
||||
|
||||
buttonPane.add(select);
|
||||
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
c.gridx = 1;
|
||||
c.gridy = 0;
|
||||
c.fill = GridBagConstraints.VERTICAL;
|
||||
|
||||
final JButton button = new JButton(AllIcons.General.Run);
|
||||
button.addActionListener(e -> {
|
||||
if (isRunning) {
|
||||
button.setIcon(AllIcons.General.Run);
|
||||
isRunning = false;
|
||||
stopTrace();
|
||||
}
|
||||
else {
|
||||
button.setIcon(AllIcons.Actions.Suspend);
|
||||
isRunning = true;
|
||||
startTrace();
|
||||
}
|
||||
});
|
||||
|
||||
buttonPane.add(button);
|
||||
add(buttonPane, c);
|
||||
c.weightx = 1.0;
|
||||
c.gridx = 2;
|
||||
c.gridy = 0;
|
||||
c.anchor = GridBagConstraints.EAST;
|
||||
|
||||
final JButton config = new JButton(AllIcons.General.Gear);
|
||||
config.addActionListener(e -> authorize(true));
|
||||
|
||||
add(config, c);
|
||||
|
||||
c.anchor = GridBagConstraints.CENTER;
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 4;
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
c.weighty = 1;
|
||||
outputArea = new JTextArea(30, 120);
|
||||
add(new JBScrollPane(outputArea), c);
|
||||
|
||||
|
||||
try {
|
||||
tmpDir = FileUtil.createTempDirectory("jtr", "scr", true);
|
||||
File f = new File(tmpDir, "lwawt.d");
|
||||
if (!f.createNewFile()) {
|
||||
outputArea.setText(f.getAbsolutePath() + " not created");
|
||||
}
|
||||
else {
|
||||
final InputStream resourceAsStream =
|
||||
getClass().getClassLoader().getResourceAsStream("com/intellij/internal/performance/profiler/dtrace/lwawt.d");
|
||||
final FileOutputStream outputStream = new FileOutputStream(f);
|
||||
|
||||
try {
|
||||
FileUtil.copy(resourceAsStream, outputStream);
|
||||
}
|
||||
finally {
|
||||
StreamUtil.closeStream(resourceAsStream);
|
||||
StreamUtil.closeStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
lwawtScr = f.getAbsolutePath();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
pack();
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static SudoAuthDialog showAuthDialog() {
|
||||
final Ref<SudoAuthDialog> dialog = Ref.create();
|
||||
ApplicationManager.getApplication().invokeAndWait(() -> {
|
||||
char[] cred = getCredentials();
|
||||
dialog.set(new SudoAuthDialog());
|
||||
dialog.get().setup(cred != null ? cred : new char[]{});
|
||||
dialog.get().showAndGet();
|
||||
}, ModalityState.any());
|
||||
return dialog.get();
|
||||
}
|
||||
|
||||
|
||||
private void startTrace() {
|
||||
try {
|
||||
outputData = false;
|
||||
|
||||
|
||||
if (!authorize(false)) return;
|
||||
|
||||
PtyCommandLine dtraceCmd = new PtyCommandLine();
|
||||
dtraceCmd.setExePath("sudo");
|
||||
dtraceCmd.addParameter("--reset-timestamp");
|
||||
dtraceCmd.addParameter("dtrace");
|
||||
dtraceCmd.addParameter("-q");
|
||||
dtraceCmd.addParameter("-p");
|
||||
dtraceCmd.addParameter(pidField.getText());
|
||||
dtraceCmd.addParameter("-s");
|
||||
dtraceCmd.addParameter(lwawtScr);
|
||||
|
||||
p = dtraceCmd
|
||||
.startProcessWithPty(
|
||||
CommandLineUtil.toCommandLine(dtraceCmd.getExePath(), dtraceCmd.getParametersList().getList()), false);
|
||||
myProcessHandler = new OSProcessHandler(p, dtraceCmd.toString());
|
||||
myProcessHandler.setHasPty(true);
|
||||
myProcessHandler.addProcessListener(new ProcessAdapter() {
|
||||
@Override
|
||||
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
|
||||
if (outputType == ProcessOutputTypes.STDOUT) {
|
||||
if (event.getText().contains("Sorry, try again.")) {
|
||||
authorize(true);
|
||||
try {
|
||||
mySink.write((String.copyValueOf(myPasswd) + "\n").getBytes(Charset.forName("UTF-8")));
|
||||
mySink.flush();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
else if (event.getText().contains("</data>")) {
|
||||
outputData = false;
|
||||
}
|
||||
else if (!event.getText().contains("<data>")) {
|
||||
if (rememberPasswd) {
|
||||
saveCredentials(myPasswd);
|
||||
}
|
||||
if (outputData) {
|
||||
outputArea.setText(outputArea.getText() + event.getText());
|
||||
}
|
||||
}
|
||||
else {
|
||||
outputData = true;
|
||||
outputArea.setText("");
|
||||
}
|
||||
}
|
||||
else if (outputType == ProcessOutputTypes.STDERR) {
|
||||
LOG.info("STDERR: " + event.getText());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mySink = myProcessHandler.getProcessInput();
|
||||
|
||||
mySink.write((String.copyValueOf(myPasswd) + "\n").getBytes(Charset.forName("UTF-8")));
|
||||
mySink.flush();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
|
||||
myProcessHandler.startNotify();
|
||||
}
|
||||
|
||||
private boolean authorize(boolean reset) {
|
||||
char[] cred = getCredentials();
|
||||
if (cred == null || reset) {
|
||||
|
||||
SudoAuthDialog dialog = showAuthDialog();
|
||||
|
||||
if (dialog == null || !dialog.isOK()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
myPasswd = dialog.getPassword();
|
||||
rememberPasswd = dialog.isRememberPassword();
|
||||
if (rememberPasswd) {
|
||||
saveCredentials(myPasswd);
|
||||
}
|
||||
}
|
||||
else {
|
||||
myPasswd = cred;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
final SpeedSearchFilter<ProcessInfo> filter = new SpeedSearchFilter<ProcessInfo>() {
|
||||
@Override
|
||||
public boolean canBeHidden(ProcessInfo value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIndexedString(ProcessInfo value) {
|
||||
return value.getPid() +
|
||||
" " +
|
||||
(value.getExecutableCannonicalPath().isPresent()
|
||||
? value.getExecutableCannonicalPath().get()
|
||||
: value.getExecutableDisplayName());
|
||||
}
|
||||
};
|
||||
|
||||
private void selectProcess() {
|
||||
final ProcessInfo[] processList = OSProcessUtil.getProcessList();
|
||||
|
||||
ListPopupStep<ProcessInfo> step = new ListPopupStep<ProcessInfo>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public List<ProcessInfo> getValues() {
|
||||
return Arrays.asList(processList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectable(ProcessInfo value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Icon getIconFor(ProcessInfo aValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getTextFor(ProcessInfo value) {
|
||||
return value.getPid() +
|
||||
" " +
|
||||
(value.getExecutableCannonicalPath().isPresent()
|
||||
? value.getExecutableCannonicalPath().get()
|
||||
: value.getExecutableDisplayName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ListSeparator getSeparatorAbove(ProcessInfo value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultOptionIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PopupStep onChosen(ProcessInfo selectedValue, boolean finalChoice) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubstep(ProcessInfo selectedValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void canceled() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMnemonicsNavigationEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MnemonicNavigationFilter<ProcessInfo> getMnemonicNavigationFilter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpeedSearchEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SpeedSearchFilter<ProcessInfo> getSpeedSearchFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoSelectionEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Runnable getFinalRunnable() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
final ListPopup popup = JBPopupFactory.getInstance().createListPopup(step);
|
||||
final JList mainList = ((ListPopupImpl)popup).getList();
|
||||
|
||||
ListSelectionListener listener = event -> {
|
||||
if (event.getValueIsAdjusting()) return;
|
||||
|
||||
Object item = ((JList)event.getSource()).getSelectedValue();
|
||||
pidField.setText(Integer.toString(((ProcessInfo)item).getPid()));
|
||||
};
|
||||
popup.addListSelectionListener(listener);
|
||||
|
||||
// force first valueChanged event
|
||||
listener.valueChanged(new ListSelectionEvent(mainList, mainList.getMinSelectionIndex(), mainList.getMaxSelectionIndex(), false));
|
||||
|
||||
popup.showInFocusCenter();
|
||||
}
|
||||
|
||||
private void stopTrace() {
|
||||
try {
|
||||
mySink.write(3);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setPassword(char[] password) {
|
||||
myPasswd = password;
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.internal.performance.profiler.dtrace;
|
||||
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.awt.*;
|
||||
|
||||
public class SudoAuthDialog extends DialogWrapper implements DocumentListener {
|
||||
private JPasswordField myPasswordField;
|
||||
private JCheckBox myRememberPasswordBox;
|
||||
|
||||
private char[] myPassword;
|
||||
|
||||
public SudoAuthDialog() {
|
||||
super(true);
|
||||
setResizable(false);
|
||||
}
|
||||
|
||||
public void setup(char[] password) {
|
||||
myPassword = password;
|
||||
getHelpAction().setEnabled(false);
|
||||
init();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Action[] createActions() {
|
||||
return new Action[]{getOKAction(), getCancelAction(), getHelpAction()};
|
||||
}
|
||||
|
||||
protected JComponent createCenterPanel() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new GridBagLayout());
|
||||
|
||||
GridBagConstraints gb = new GridBagConstraints();
|
||||
|
||||
// top label.
|
||||
gb.insets = JBUI.insets(2);
|
||||
gb.weightx = 1;
|
||||
gb.weighty = 0;
|
||||
gb.gridwidth = 2;
|
||||
gb.gridheight = 1;
|
||||
gb.gridx = 0;
|
||||
gb.gridy = 0;
|
||||
gb.anchor = GridBagConstraints.WEST;
|
||||
gb.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
JLabel label = new JLabel("Privileged access is required");
|
||||
panel.add(label, gb);
|
||||
|
||||
// user name
|
||||
gb.gridy += 1;
|
||||
gb.gridwidth = 1;
|
||||
gb.weightx = 0;
|
||||
gb.fill = GridBagConstraints.NONE;
|
||||
|
||||
label = new JLabel("Password:");
|
||||
panel.add(label, gb);
|
||||
|
||||
// user name field
|
||||
gb.gridx = 1;
|
||||
gb.weightx = 1;
|
||||
gb.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
myPasswordField = new JPasswordField();
|
||||
panel.add(myPasswordField, gb);
|
||||
label.setLabelFor(myPasswordField);
|
||||
|
||||
if (myPassword != null) {
|
||||
myPasswordField.setText(String.valueOf(myPassword));
|
||||
}
|
||||
myPasswordField.selectAll();
|
||||
myPasswordField.getDocument().addDocumentListener(this);
|
||||
|
||||
gb.gridy += 1;
|
||||
gb.gridx = 0;
|
||||
gb.gridwidth = 2;
|
||||
gb.weightx = 1;
|
||||
gb.fill = GridBagConstraints.HORIZONTAL;
|
||||
myRememberPasswordBox = new JCheckBox("Remember");
|
||||
panel.add(myRememberPasswordBox, gb);
|
||||
gb.gridy += 1;
|
||||
panel.add(new JSeparator(), gb);
|
||||
|
||||
myRememberPasswordBox.setSelected(false);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
protected String getDimensionServiceKey() {
|
||||
return "svn.userNameDialog";
|
||||
}
|
||||
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myPasswordField;
|
||||
}
|
||||
|
||||
public boolean shouldCloseOnCross() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isOKActionEnabled() {
|
||||
return myPasswordField != null && myPasswordField.getPassword().length != 0;
|
||||
}
|
||||
|
||||
public char[] getPassword() {
|
||||
return isOK() && myPasswordField != null ? myPasswordField.getPassword() : null;
|
||||
}
|
||||
|
||||
public boolean isRememberPassword() {
|
||||
return myRememberPasswordBox.isSelected();
|
||||
}
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
updateOKButton();
|
||||
}
|
||||
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
updateOKButton();
|
||||
}
|
||||
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
updateOKButton();
|
||||
}
|
||||
|
||||
private void updateOKButton() {
|
||||
getOKAction().setEnabled(isOKActionEnabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma option quiet
|
||||
|
||||
self long long ts[string];
|
||||
|
||||
pid$target:libawt_lwawt::entry /self->ts[probefunc] == 0/
|
||||
{
|
||||
self->ts[probefunc] = timestamp;
|
||||
}
|
||||
|
||||
pid$target:libawt_lwawt::return /self->ts[probefunc]/
|
||||
{
|
||||
this->tt = timestamp - self->ts[probefunc];
|
||||
self->ts[probefunc] = 0;
|
||||
|
||||
@t[ufunc(uregs[R_PC])] = sum(this->tt);
|
||||
}
|
||||
|
||||
tick-1sec {
|
||||
printf("<data>\n");
|
||||
normalize(@t, 1000000);
|
||||
trunc(@t, 20);
|
||||
printa(@t);
|
||||
printf("</data>\n");
|
||||
}
|
||||
|
||||
END {
|
||||
printf("<data>\n");
|
||||
normalize(@t, 1000000);
|
||||
trunc(@t, 20);
|
||||
printa(@t);
|
||||
printf("</data>\n");
|
||||
}
|
||||
@@ -797,6 +797,11 @@
|
||||
<reference ref="MaintenanceGroup"/>
|
||||
|
||||
<action internal="true" id="SendStatistics" class="com.intellij.internal.statistic.actions.SendStatisticsAction" text="Collect And Send Statistics"/>
|
||||
|
||||
<group id="Internal.Profile" popup="true" text="Profiler">
|
||||
<action id="DTraceAction" internal="true" class="com.intellij.internal.performance.profiler.dtrace.DTraceAction" text="DTrace lwawt"/>
|
||||
</group>
|
||||
|
||||
<add-to-group group-id="ToolsMenu" anchor="last"/>
|
||||
</group>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user