Merge remote-tracking branch 'origin/GP-126_ghidra1_StoreCumulativeAnalaysisTimes'

This commit is contained in:
ghidra1 2020-09-18 16:28:59 -04:00
commit 8af6deefb5
8 changed files with 397 additions and 55 deletions

View File

@ -802,6 +802,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
notifyAnalysisEnded();
if (printTaskTimes) {
printTimedTasks();
saveTaskTimes();
}
}
}
@ -1289,6 +1290,19 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
Msg.info(this, taskTimeString);
}
private void saveTaskTimes() {
StoredAnalyzerTimes times = StoredAnalyzerTimes.getStoredAnalyzerTimes(program);
String taskNames[] = getTimedTasks();
for (String element : taskNames) {
long taskTimeMSec = getTaskTime(timedTasks, element);
times.addTime(element, taskTimeMSec);
}
StoredAnalyzerTimes.setStoredAnalyzerTimes(program, times);
}
/**
* Schedule an analysis worker to run while auto analysis is suspended. Invocation will block
* until callback is completed or cancelled. If an analysis task is busy, it will be allowed to

View File

@ -32,6 +32,7 @@ import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.Analyzer;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@ -275,6 +276,11 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis
}
private void programActivated(final Program program) {
program.getOptions(StoredAnalyzerTimes.OPTIONS_LIST)
.registerOption(StoredAnalyzerTimes.OPTION_NAME, OptionType.CUSTOM_TYPE,
null, null, "Cumulative analysis task times",
new StoredAnalyzerTimesPropertyEditor());
// invokeLater() to ensure that all other plugins have been notified of the program
// activated. This makes sure plugins like the Listing have opened and painted the

View File

@ -0,0 +1,170 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.plugin.core.analysis;
import java.util.*;
import ghidra.framework.options.*;
import ghidra.program.model.listing.Program;
/**
* <code>StoredAnalyzerTimes</code> provides a custom option container for
* accumulated analysis times for named tasks.
*/
public class StoredAnalyzerTimes implements CustomOption {
public static final String OPTIONS_LIST = Program.PROGRAM_INFO + ".Analysis Times";
public static final String OPTION_NAME = "Times";
private Map<String, Long> taskTimes = new HashMap<>();
private Long totalTime;
private String[] names;
@Override
public void readState(SaveState saveState) {
taskTimes.clear();
for (String taskName : saveState.getNames()) {
taskTimes.put(taskName, saveState.getLong(taskName, 0));
}
names = null;
totalTime = null;
}
@Override
public void writeState(SaveState saveState) {
for (String taskName : taskTimes.keySet()) {
saveState.putLong(taskName, taskTimes.get(taskName));
}
}
/**
* Clear all task entries and times
*/
public void clear() {
taskTimes.clear();
names = null;
totalTime = null;
}
/**
* Determine if any task times exist
* @return true if no task times available, else false
*/
public boolean isEmpty() {
return taskTimes.isEmpty();
}
/**
* Clear time entry corresponding to specified taskName
* @param taskName analysis task name
*/
public void clear(String taskName) {
taskTimes.remove(taskName);
names = null;
totalTime = null;
}
/**
* Add the specified time corresponding to the specified analysis taskName
* @param taskName analysis task name
* @param t time increment
*/
public void addTime(String taskName, long t) {
long cumulativeTime = taskTimes.getOrDefault(taskName, 0L) + t;
taskTimes.put(taskName, cumulativeTime);
names = null;
totalTime = null;
}
/**
* Get the accumulated time for the specified analysis taskName
* @param taskName analysis task name
* @return accumulated task time or null if entry not found
*/
public Long getTime(String taskName) {
return taskTimes.get(taskName);
}
/**
* Get the total accumulated task time for all task entries
* @return total accumuated task time
*/
public long getTotalTime() {
if (totalTime == null) {
long sum = 0;
for (long t : taskTimes.values()) {
sum += t;
}
totalTime = sum;
}
return totalTime;
}
/**
* Get all task names for which time entries exist
* @return array of task names
*/
public String[] getTaskNames() {
if (names == null) {
names = taskTimes.keySet().toArray(new String[taskTimes.size()]);
Arrays.sort(names);
}
return names;
}
@Override
public int hashCode() {
return taskTimes.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StoredAnalyzerTimes)) {
return false;
}
return taskTimes.equals(((StoredAnalyzerTimes) obj).taskTimes);
}
@Override
public StoredAnalyzerTimes clone() {
StoredAnalyzerTimes newInstance = new StoredAnalyzerTimes();
newInstance.taskTimes = new HashMap<>(taskTimes);
return newInstance;
}
/**
* Get the StoredAnalyzerTimes options data from the specified program
* @param program program
* @return StoredAnalyzerTimes option data
*/
public static StoredAnalyzerTimes getStoredAnalyzerTimes(Program program) {
Options options = program.getOptions(OPTIONS_LIST);
StoredAnalyzerTimes times = (StoredAnalyzerTimes) options
.getCustomOption(StoredAnalyzerTimes.OPTION_NAME, new StoredAnalyzerTimes());
return times;
}
/**
* Set the updated StoredAnalyzerTimes option data on the specified program
* @param program program
* @param times StoredAnalyzerTimes option data
*/
public static void setStoredAnalyzerTimes(Program program, StoredAnalyzerTimes times) {
Options options = program.getOptions(OPTIONS_LIST);
options.putObject(StoredAnalyzerTimes.OPTION_NAME, times);
}
}

View File

@ -0,0 +1,131 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.app.plugin.core.analysis;
import java.awt.*;
import java.beans.PropertyEditorSupport;
import javax.swing.*;
import docking.widgets.label.GDLabel;
import ghidra.framework.options.CustomOptionsEditor;
import ghidra.util.StringUtilities;
import ghidra.util.layout.PairLayout;
/**
* <code>StoredAnalyzerTimesPropertyEditor</code> implements a custom option
* editor panel for {@link StoredAnalyzerTimes}. Ability to edit values
* is disabled with panel intended for display purpose only.
*/
public class StoredAnalyzerTimesPropertyEditor extends PropertyEditorSupport
implements CustomOptionsEditor {
private StoredAnalyzerTimes times;
@Override
public boolean supportsCustomEditor() {
return true;
}
@Override
public String[] getOptionNames() {
if (times == null) {
return new String[0];
}
return times.getTaskNames();
}
@Override
public String[] getOptionDescriptions() {
return null;
}
@Override
public void setValue(Object value) {
if (!(value instanceof StoredAnalyzerTimes)) {
return;
}
times = (StoredAnalyzerTimes) value;
firePropertyChange();
}
@Override
public Object getValue() {
return times.clone();
}
@Override
public Component getCustomEditor() {
return buildEditor();
}
/**
* Build analysis time panel showing all task names and corresponding
* cumulative times in seconds. Edit ability is disabled.
* @return options panel
*/
private Component buildEditor() {
if (times == null || times.isEmpty()) {
JPanel panel = new JPanel(new FlowLayout());
panel.add(new JLabel("No Data Available"));
return panel;
}
JPanel panel = new JPanel(new PairLayout(6, 10));
panel.add(new GDLabel(""));
GDLabel label = new GDLabel("seconds", SwingConstants.RIGHT);
panel.add(label);
for (String taskName : getOptionNames()) {
label = new GDLabel(taskName, SwingConstants.RIGHT);
label.setToolTipText(taskName);
panel.add(label);
Long timeMS = times.getTime(taskName);
if (timeMS == null) {
continue;
}
JTextField valueField = new JTextField(formatTimeMS(timeMS));
valueField.setEditable(false);
valueField.setHorizontalAlignment(SwingConstants.RIGHT);
panel.add(valueField);
}
label = new GDLabel("TOTAL", SwingConstants.RIGHT);
label.setFont(label.getFont().deriveFont(Font.BOLD));
panel.add(label);
JTextField valueField = new JTextField(formatTimeMS(times.getTotalTime()));
valueField.setEditable(false);
valueField.setHorizontalAlignment(SwingConstants.RIGHT);
valueField.setBorder(BorderFactory.createLineBorder(Color.black, 2));
panel.add(valueField);
return panel;
}
private String formatTimeMS(long timeMS) {
String str = Long.toUnsignedString(timeMS / 1000L);
str += ".";
str += StringUtilities.pad(Long.toUnsignedString(timeMS % 1000L), '0', 3);
return str;
}
}

View File

@ -29,6 +29,7 @@ import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
import utilities.util.reflection.ReflectionUtilities;
public abstract class AbstractOptions implements Options {
@ -143,64 +144,51 @@ public abstract class AbstractOptions implements Options {
ReflectionUtilities.createJavaFilteredThrowable());
}
Option currentOption = valueMap.get(optionName);
if (currentOption == null) {
Option option =
createRegisteredOption(optionName, type, description, help, defaultValue, editor);
valueMap.put(optionName, option);
Option currentOption = getExistingComptibleOption(optionName, type, defaultValue);
if (currentOption != null) {
currentOption.updateRegistration(description, help, defaultValue, editor);
return;
}
Option newOption = null;
if (currentOption.isRegistered()) {
// Registered again
newOption =
copyRegisteredOption(currentOption, type, description, defaultValue, help, editor);
}
else {
// option was accessed, but not registered
newOption =
createRegisteredOption(optionName, type, description, help, defaultValue, editor);
}
Option option =
createRegisteredOption(optionName, type, description, help, defaultValue, editor);
copyCurrentValue(currentOption, newOption);
valueMap.put(optionName, newOption);
valueMap.put(optionName, option);
}
protected void copyCurrentValue(Option currentOption, Option newOption) {
if (currentOption.isDefault()) {
return; // don't copy the current value if it is just the old default.
private Option getExistingComptibleOption(String optionName, OptionType type,
Object defaultValue) {
// There are several cases where an existing option may exist when registering an option
// 1) the option was accessed before it was registered
// 2) the option was loaded from a store (database or toolstate)
// 3) the option was registered more than once.
//
// The only time this is a problem is if the exiting option type is not compatible with
// the type being registered. If we encounter an incompatible option, we just log a
// warning and return null so that the new option will replace it. Otherwise, we return
// the existing option so it can be updated with the data from the registration.
Option option = valueMap.get(optionName);
if (option == null) {
return null;
}
Object currentValue = currentOption.getCurrentValue();
OptionType type = currentOption.getOptionType();
if (!isNullable(type) && currentValue == null) {
return; // not allowed to be null
if (!isCompatibleOption(option, type, defaultValue)) {
Msg.error(this, "Registered option incompatible with existing option: " + optionName,
new AssertException());
return null;
}
// null is allowed; null can represent a valid 'cleared' state
newOption.setCurrentValue(currentValue);
return option;
}
private Option copyRegisteredOption(Option currentOption, OptionType type,
String description, Object defaultValue, HelpLocation help, PropertyEditor editor) {
// We probably don't need to do anything special if we are re-registering an option,
// which is what the below code handles
String oldDescription = currentOption.getDescription();
HelpLocation oldHelp = currentOption.getHelpLocation();
Object oldDefaultValue = currentOption.getDefaultValue();
PropertyEditor oldEditor = currentOption.getPropertyEditor();
String newDescripiton = oldDescription == null ? description : oldDescription;
HelpLocation newHelpLocation = oldHelp == null ? help : oldHelp;
Object newDefaultValue = oldDefaultValue == null ? defaultValue : oldDefaultValue;
PropertyEditor newEditor = oldEditor == null ? editor : oldEditor;
String optionName = currentOption.getName();
return createRegisteredOption(optionName, type, newDescripiton, newHelpLocation,
newDefaultValue, newEditor);
private boolean isCompatibleOption(Option option, OptionType type, Object defaultValue) {
if (option.getOptionType() != type) {
return false;
}
Object optionValue = option.getValue(null);
return optionValue == null || defaultValue == null ||
optionValue.getClass().equals(defaultValue.getClass());
}
@Override

View File

@ -20,6 +20,7 @@ import java.beans.*;
import java.util.HashSet;
import java.util.Set;
import ghidra.framework.Application;
import ghidra.util.SystemUtilities;
public class EditorState implements PropertyChangeListener {
@ -119,10 +120,16 @@ public class EditorState implements PropertyChangeListener {
* directly, as opposed to using the generic framework.
*/
public boolean supportsCustomOptionsEditor() {
return (editor instanceof CustomOptionsEditor);
return editor == null || (editor instanceof CustomOptionsEditor);
}
public Component getEditorComponent() {
if (editor == null) {
// can occur if support has been dropped for custom state/option
editor = new ErrorPropertyEditor(
"Ghidra does not know how to render state: " + name, null);
return editor.getCustomEditor();
}
if (editor.supportsCustomEditor()) {
return editor.getCustomEditor();
}
@ -146,7 +153,9 @@ public class EditorState implements PropertyChangeListener {
editor.removePropertyChangeListener(this);
editor = new ErrorPropertyEditor(
"Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName(), null);
Application.getName() + " does not know how to use PropertyEditor: " +
editor.getClass().getName(),
null);
return editor.getCustomEditor();
}

View File

@ -24,13 +24,13 @@ import utilities.util.reflection.ReflectionUtilities;
public abstract class Option {
private final String name;
private final Object defaultValue;
private Object defaultValue;
private boolean isRegistered;
private final String description;
private final HelpLocation helpLocation;
private final OptionType optionType;
private String description;
private HelpLocation helpLocation;
private OptionType optionType;
private final PropertyEditor propertyEditor;
private PropertyEditor propertyEditor;
private String inceptionInformation;
protected Option(String name, OptionType optionType, String description,
@ -48,6 +48,25 @@ public abstract class Option {
}
}
/**
* Update any null registration information using the specified updated information.
* This assumption is that multiple registrations for the same option may be partial
* but will be compatible. No compatibility checks are performed between existing
* registration and update registration info.
* @param updatedDescription updated option description (will be ignored if description previously set)
* @param updatedHelp updated option help location (will be ignored if help location previously set)
* @param updatedDefaultValue updated option default-value (will be ignored if default-value previously set)
* @param updatedEditor updated option editor (will be ignored if editor previously set)
*/
final void updateRegistration(String updatedDescription, HelpLocation updatedHelp,
Object updatedDefaultValue, PropertyEditor updatedEditor) {
description = description != null ? description : updatedDescription;
helpLocation = helpLocation != null ? helpLocation : updatedHelp;
defaultValue = defaultValue != null ? defaultValue : updatedDefaultValue;
propertyEditor = propertyEditor != null ? propertyEditor : updatedEditor;
isRegistered = true;
}
public abstract Object getCurrentValue();
public abstract void doSetCurrentValue(Object value);

View File

@ -223,8 +223,13 @@ public enum OptionType {
option.readState(saveState);
return option;
}
catch (ClassNotFoundException e) {
Msg.warn(this,
"Ignoring unsupported customOption instance for: " + customOptionClassName);
}
catch (Exception e) {
Msg.error(this, "Can't create customOption instance for: " + customOptionClassName,
Msg.error(this,
"Can't create customOption instance for: " + customOptionClassName +
e);
}
return null;