00001
00004 package edu.rice.cs.hpc.viewer.metric;
00005
00006 import java.util.Formatter;
00007 import java.util.FormatterClosedException;
00008 import java.util.IllegalFormatException;
00009
00010 import org.eclipse.jface.dialogs.TitleAreaDialog;
00011 import org.eclipse.jface.dialogs.MessageDialog;
00012 import org.eclipse.jface.layout.GridDataFactory;
00013 import org.eclipse.jface.layout.GridLayoutFactory;
00014 import org.eclipse.jface.layout.LayoutConstants;
00015
00016 import org.eclipse.swt.SWT;
00017 import org.eclipse.swt.graphics.Point;
00018 import org.eclipse.swt.widgets.Composite;
00019 import org.eclipse.swt.widgets.Control;
00020 import org.eclipse.swt.widgets.ExpandBar;
00021 import org.eclipse.swt.widgets.ExpandItem;
00022 import org.eclipse.swt.widgets.Group;
00023 import org.eclipse.swt.widgets.Shell;
00024 import org.eclipse.swt.widgets.Label;
00025 import org.eclipse.swt.widgets.Combo;
00026 import org.eclipse.swt.widgets.Button;
00027 import org.eclipse.swt.widgets.Text;
00028 import org.eclipse.swt.events.KeyAdapter;
00029 import org.eclipse.swt.events.KeyEvent;
00030 import org.eclipse.swt.events.MouseAdapter;
00031 import org.eclipse.swt.events.MouseEvent;
00032 import org.eclipse.swt.events.SelectionListener;
00033 import org.eclipse.swt.events.SelectionEvent;
00034
00035 import edu.rice.cs.hpc.common.util.UserInputHistory;
00036 import edu.rice.cs.hpc.data.experiment.Experiment;
00037 import edu.rice.cs.hpc.data.experiment.metric.*;
00038 import edu.rice.cs.hpc.data.experiment.metric.BaseMetric.AnnotationType;
00039 import edu.rice.cs.hpc.data.experiment.scope.Scope;
00040
00041 import com.graphbuilder.math.*;
00042 import com.graphbuilder.math.func.*;
00043
00048 public class ExtDerivedMetricDlg extends TitleAreaDialog {
00049
00050 static public enum MetricDisplayFormat {
00051 Default, Percent, Custom
00052 }
00053
00054
00055
00056 final private String FORMAT_PERCENT = "%.2f %%";
00057
00058
00059 private Combo cbName;
00060 private Combo cbExpression;
00061 private Button btnPercent;
00062 private Text txtFormat;
00063 private Button btnCustomFormat;
00064 private Button btnPercentFormat;
00065 private Button btnDefaultFormat;
00066
00067
00068 private String []arrStrMetrics;
00069 private Expression expFormula;
00070 private final ExtFuncMap fctMap;
00071 private final MetricVarMap varMap;
00072 private DerivedMetric metric;
00073
00074
00075 static private final String HISTORY_FORMULA = "formula";
00076 static private final String HISTORY_METRIC_NAME = "metric_name";
00077 private Experiment experiment;
00078 private Point expression_position;
00079
00080
00081 private UserInputHistory objHistoryFormula;
00082 private UserInputHistory objHistoryName;
00083
00084
00085
00086
00092 public ExtDerivedMetricDlg(Shell parentShell, Experiment exp) {
00093 this(parentShell, exp, null);
00094 }
00095
00096 public ExtDerivedMetricDlg(Shell parent, Experiment exp, Scope s) {
00097 super(parent);
00098 experiment = exp;
00099 this.setMetrics(exp.getMetrics());
00100 this.fctMap = new ExtFuncMap(exp.getMetrics());
00101 this.varMap = new MetricVarMap ( s, exp );
00102 }
00103
00104
00105
00106
00107
00114 protected Control createContents(Composite parent) {
00115 Control contents = super.createContents(parent);
00116
00117
00118 setTitle("Creating a derived metric");
00119
00120
00121 setMessage("A derived metric is a spreadsheet-like formula using other metrics (variables), operators, functions,\n"
00122 + "and numerical constants.\n");
00123
00124 return contents;
00125 }
00126
00127
00128
00129
00130
00131 protected Control createDialogArea(Composite parent) {
00132 Composite composite = (Composite) super.createDialogArea(parent);
00133 Group grpBase = new Group(composite, SWT.NONE);
00134 grpBase.setText("Derived metric definition");
00135
00136 Point ptMargin = LayoutConstants.getMargins();
00137 ptMargin.x = 5;
00138 ptMargin.y = 5;
00139
00140 Composite expressionArea = new Composite(grpBase, SWT.NONE);
00141 {
00142 Group grpExpression = new Group(expressionArea, SWT.NONE);
00143
00144
00145
00146
00147 final Composite nameArea = new Composite(grpExpression, SWT.NONE);
00148 final Label lblName = new Label(nameArea, SWT.LEFT);
00149 lblName.setText("Name:");
00150
00151 this.cbName = new Combo(nameArea, SWT.NONE);
00152 this.cbName.setToolTipText("Name of the derived metric");
00153 objHistoryName = new UserInputHistory( ExtDerivedMetricDlg.HISTORY_METRIC_NAME );
00154 this.cbName.setItems( this.objHistoryName.getHistory() );
00155
00156 if (metric != null) {
00157 cbName.setText(metric.getDisplayName());
00158 }
00159
00160
00161
00162
00163 Label lblFormula = new Label(nameArea, SWT.NONE);
00164 lblFormula.setText("Formula: ");
00165
00166 this.cbExpression = new Combo(nameArea, SWT.NONE);
00167 objHistoryFormula = new UserInputHistory(HISTORY_FORMULA);
00168 this.cbExpression.setItems( objHistoryFormula.getHistory() );
00169 cbExpression.setToolTipText("A spreadsheet-like formula using other metrics (variables), arithmetic operators, functions, and numerical constants");
00170
00171 if (metric != null) {
00172 cbExpression.setText( metric.getFormula().toString() );
00173 }
00174
00175 GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(nameArea);
00176
00177 Label lbl = new Label(grpExpression, SWT.WRAP);
00178 lbl.setText("There are two kinds of metric variables: point-wise and aggregate. The former is like a spreadsheet cell, the "
00179 + "latter is like a spreadsheet-column sum. To form a variable, prepend '$' and '@', respectively, to a metric id. "
00180 + "For instance, the formula\n"
00181 + " (($2 - $1) * 100.0) / @1\n"
00182 + "divides the scaled difference of the point-wise metrics 2 and 1 by the aggregate value of metric 1.");
00183
00184 expression_position = new Point(0,0);
00185 cbExpression.addKeyListener( new KeyAdapter(){
00186
00187 public void keyReleased(KeyEvent e) {
00188 expression_position = cbExpression.getSelection();
00189 }
00190
00191 });
00192
00193 cbExpression.addMouseListener( new MouseAdapter(){
00194 public void mouseUp(MouseEvent e) {
00195 if (cbExpression.getClientArea().contains(e.x, e.y)) {
00196 expression_position = cbExpression.getSelection();
00197 }
00198 }
00199
00200 });
00201
00202 GridLayoutFactory.fillDefaults().numColumns(1).generateLayout(grpExpression);
00203
00204
00205 Group grpInsertion = new Group(expressionArea, SWT.NONE);
00206 grpInsertion.setText("Assistance:");
00207
00208 Label lblMetric = new Label(grpInsertion, SWT.NONE);
00209 lblMetric.setText("Metrics:");
00210
00211
00212 final Combo cbMetric = new Combo(grpInsertion, SWT.READ_ONLY);
00213 cbMetric.setItems(this.arrStrMetrics);
00214 cbMetric.setText(this.arrStrMetrics[0]);
00215
00216
00217
00218
00219
00220 final Composite buttonArea = new Composite(grpInsertion, SWT.NONE);
00221 final Button btnMetric = new Button(buttonArea, SWT.PUSH);
00222 btnMetric.setText("Point-wise");
00223 btnMetric.setToolTipText("Insert the metric as point-wise variable in the formula by prepending with '$' sign");
00224
00225 btnMetric.addSelectionListener(new SelectionListener() {
00226 public void widgetSelected(SelectionEvent e) {
00227 insertMetricToFormula("$", cbMetric.getSelectionIndex());
00228 }
00229 public void widgetDefaultSelected(SelectionEvent e) {
00230
00231 }
00232 });
00233
00234 final Button btnAggregate = new Button(buttonArea, SWT.PUSH);
00235 btnAggregate.setText("Aggregate");
00236 btnAggregate.setToolTipText("Insert the metric as aggregate variable in the formula by prepending with '@' sign");
00237
00238 btnAggregate.addSelectionListener(new SelectionListener() {
00239 public void widgetSelected(SelectionEvent e) {
00240 insertMetricToFormula("@", cbMetric.getSelectionIndex());
00241 }
00242 public void widgetDefaultSelected(SelectionEvent e) {
00243
00244 }
00245 });
00246 GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(buttonArea);
00247
00248
00249 Label lblFunc = new Label(grpInsertion, SWT.NONE);
00250 lblFunc.setText("Functions:");
00251
00252 final Combo cbFunc = new Combo(grpInsertion, SWT.READ_ONLY);
00253 fctMap.loadDefaultFunctions();
00254 Function arrFct[] = fctMap.getFunctions();
00255
00256
00257
00258 final String []arrFunctions = new String[arrFct.length];
00259
00260 final String []arrFuncNames = fctMap.getFunctionNames();
00261 for(int i=0;i<arrFct.length;i++) {
00262 arrFunctions[i] = arrFct[i].toString();
00263 }
00264
00265 if(arrFunctions != null && arrFunctions.length>0) {
00266 cbFunc.setItems(arrFunctions);
00267
00268 cbFunc.setText(arrFunctions[0]);
00269 }
00270
00271 final Button btnFunc = new Button(grpInsertion, SWT.PUSH);
00272 btnFunc.setText("Insert function");
00273 btnFunc.addSelectionListener(new SelectionListener() {
00274
00275 public void widgetSelected(SelectionEvent e) {
00276 Point p = expression_position;
00277 String sFunc = arrFuncNames[cbFunc.getSelectionIndex()];
00278 StringBuffer sb = new StringBuffer( cbExpression.getText() );
00279 int iLen = sFunc.length();
00280 sb.insert( p.x, sFunc );
00281 sb.insert( p.x + iLen, "()" );
00282 p.x = p.x + iLen + 1;
00283 p.y = p.x;
00284 cbExpression.setText( sb.toString() );
00285 cbExpression.setSelection( p );
00286 }
00287 public void widgetDefaultSelected(SelectionEvent e) {
00288
00289 }
00290 });
00291
00292 final Label lblOperators = new Label(grpInsertion, SWT.NONE);
00293 lblOperators.setText("Operators: ");
00294 final Label lblOpValues = new Label(grpInsertion, SWT.NONE);
00295 lblOpValues.setText("( ) + - * / ^");
00296
00297
00298
00299 GridDataFactory.fillDefaults().grab(false, false).applyTo(grpInsertion);
00300 GridLayoutFactory.fillDefaults().numColumns(3).generateLayout(grpInsertion);
00301
00302 GridLayoutFactory.fillDefaults().numColumns(1).generateLayout(expressionArea);
00303 }
00304 GridLayoutFactory.fillDefaults().margins(ptMargin).generateLayout(grpBase);
00305
00306
00307
00308
00309 ExpandBar barOptions = new ExpandBar(composite,SWT.V_SCROLL);
00310 {
00311 Composite cOptions = new Composite (barOptions, SWT.NONE);
00312 GridLayoutFactory.fillDefaults().numColumns(1).margins(ptMargin).generateLayout(cOptions);
00313
00314
00315 this.btnPercent = new Button(cOptions, SWT.CHECK);
00316 this.btnPercent.setText("Augment metric value display with a percentage relative to column total");
00317 this.btnPercent.setToolTipText("For each metric value, display the annotation of percentage relative to aggregate metric value");
00318
00319
00320
00321 final Composite cCustomFormat = new Composite( cOptions, SWT.NONE );
00322
00323 btnDefaultFormat = new Button(cCustomFormat, SWT.RADIO);
00324 btnDefaultFormat.setText("Default format");
00325 new Label( cCustomFormat, SWT.NONE );
00326
00327 btnPercentFormat = new Button(cCustomFormat, SWT.RADIO);
00328 btnPercentFormat.setText("Display metric value as percent");
00329 new Label( cCustomFormat, SWT.NONE );
00330
00331 btnCustomFormat = new Button(cCustomFormat, SWT.RADIO);
00332 btnCustomFormat.setText("Custom format");
00333
00334 txtFormat = new Text(cCustomFormat, SWT.BORDER);
00335 final String txtCustomFormat = "The format is based on java.util.Formatter class which is almost equivalent to C's printf format. ";
00336 txtFormat.setToolTipText(txtCustomFormat);
00337 btnCustomFormat.addSelectionListener(new SelectionListener(){
00338
00339 public void widgetSelected(SelectionEvent e) {
00340 txtFormat.setEnabled(true);
00341 }
00342
00343 public void widgetDefaultSelected(SelectionEvent e) {}
00344 });
00345
00346 btnPercentFormat.addSelectionListener(new SelectionListener(){
00347
00348 public void widgetSelected(SelectionEvent e) {
00349 txtFormat.setEnabled(false);
00350 txtFormat.setFocus();
00351 }
00352
00353 public void widgetDefaultSelected(SelectionEvent e) {}
00354
00355 });
00356 btnDefaultFormat.setSelection(true);
00357
00358 if (metric != null) {
00359 boolean bPercent = metric.getAnnotationType() == BaseMetric.AnnotationType.PERCENT;
00360 btnPercent.setSelection( bPercent );
00361
00362 IMetricValueFormat format = metric.getDisplayFormat();
00363 if (format instanceof MetricValuePredefinedFormat) {
00364 String strFormat = ((MetricValuePredefinedFormat)format ).getFormat();
00365 txtFormat.setText( strFormat );
00366
00367 if (strFormat.equals( FORMAT_PERCENT )) {
00368 btnPercentFormat.setSelection(true);
00369
00370
00371 btnDefaultFormat.setSelection(false);
00372 }
00373 }
00374 }
00375
00376
00377 txtFormat.setEnabled(false);
00378
00379 final Label lblCustomFormat = new Label( cOptions, SWT.NONE );
00380 lblCustomFormat.setText(txtCustomFormat + "\n"
00381 + "Example: '%6.2f ' will display 6 digit floating-points with 2 digit precision. ");
00382
00383 GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(cCustomFormat);
00384
00385
00386
00387
00388 ExpandItem eiOptions = new ExpandItem(barOptions, SWT.NONE, 0);
00389 eiOptions.setText("Advanced options");
00390 eiOptions.setHeight(cOptions.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
00391 eiOptions.setControl(cOptions);
00392 eiOptions.setExpanded(true);
00393
00394 barOptions.setToolTipText("Optional settings");
00395 }
00396
00397 GridLayoutFactory.fillDefaults().numColumns(1).margins(ptMargin).generateLayout(
00398 composite);
00399 return composite;
00400 }
00401
00402
00403
00404
00405
00406
00407
00408
00409 private void insertMetricToFormula(String signToPrepend, int selection_index) {
00410 final String sText = cbExpression.getText();
00411 final int iSelIndex = expression_position.x;
00412 StringBuffer sBuff = new StringBuffer(sText);
00413
00414
00415 final String sMetricIndex = signToPrepend + experiment.getMetric(selection_index).getShortName() ;
00416 sBuff.insert(iSelIndex, sMetricIndex );
00417 cbExpression.setText(sBuff.toString());
00418
00419
00420 Point p = new Point(iSelIndex + sMetricIndex.length(), iSelIndex + sMetricIndex.length());
00421 cbExpression.setSelection( p );
00422 expression_position = cbExpression.getSelection();
00423
00424 }
00425
00430 private boolean checkExpression() {
00431 boolean bResult = false;
00432 String sExpression = this.cbExpression.getText();
00433 if(sExpression.length() > 0) {
00434 try {
00435 expFormula = ExpressionTree.parse(sExpression);
00436 bResult = evaluateExpression ( this.expFormula );
00437 } catch (ExpressionParseException e) {
00438 MessageDialog.openError(this.getShell(), "Invalid expression", e.getDescription());
00439 }
00440 } else {
00441 MessageDialog.openError(this.getShell(), "Error: empty expression",
00442 "An expression can not be empty.");
00443 }
00444 return bResult;
00445 }
00446
00447
00448
00449
00450
00451 private boolean checkFormat() {
00452 boolean bResult = true;
00453 String sError = null;
00454 if (this.btnCustomFormat.getSelection()) {
00455 String sFormat = txtFormat.getText();
00456
00457
00458 if (sFormat.length()==0) {
00459 bResult = false;
00460 sError = "Custom format cannot be empty.";
00461 }
00462 try {
00463 Formatter format = new Formatter();
00464 format.format(sFormat, 1.0);
00465
00466 } catch (IllegalFormatException e) {
00467 sError = "Format is incorrect.";
00468 bResult = false;
00469 } catch (FormatterClosedException e) {
00470 bResult = false;
00471 sError = "Illegal format.";
00472 }
00473 }
00474 if (!bResult)
00475 MessageDialog.openError(getShell(), "Format syntax error", sError);
00476 return bResult;
00477 }
00478
00484 private boolean evaluateExpression ( Expression objExpression ) {
00485 try {
00486 objExpression.eval( varMap, fctMap);
00487
00488 return true;
00489
00490 } catch(java.lang.Exception e) {
00491
00492 MessageDialog.openError( this.getShell(), "Error: incorrect expression", e.getMessage());
00493 }
00494
00495 return false;
00496 }
00497
00498
00499
00500
00501
00502
00503
00504
00505 private DerivedMetric doAction() {
00506
00507 AnnotationType annType = AnnotationType.NONE;
00508
00509
00510
00511
00512
00513 if (btnPercent.getSelection()) {
00514 annType = AnnotationType.PERCENT;
00515 }
00516
00517
00518
00519
00520
00521 if (metric == null) {
00522
00523
00524
00525 int metricLastIndex = experiment.getMetricCount() -1;
00526 BaseMetric metricLast = experiment.getMetric(metricLastIndex);
00527
00528 String metricLastID = metricLast.getShortName();
00529 metricLastIndex = Integer.valueOf(metricLastID) + 1;
00530 metricLastID = String.valueOf(metricLastIndex);
00531
00532 metric = new DerivedMetric(experiment, expFormula,
00533 cbName.getText(), metricLastID, experiment.getMetricCount(),
00534 annType, MetricType.INCLUSIVE);
00535
00536 } else {
00537
00538 metric.setDisplayName( cbName.getText() );
00539 metric.setAnnotationType(annType);
00540 metric.setExpression(expFormula);
00541 }
00542
00543
00544
00545
00546
00547 final String sFormat = txtFormat.getText();
00548 IMetricValueFormat objFormat;
00549
00550 if ( btnCustomFormat.getSelection() && (sFormat != null) ) {
00551
00552
00553 objFormat = new MetricValuePredefinedFormat(sFormat);
00554 metric.setDisplayFormat(objFormat);
00555
00556 } else if (btnPercentFormat.getSelection()) {
00557 objFormat = new MetricValuePredefinedFormat(FORMAT_PERCENT);
00558 metric.setDisplayFormat(objFormat);
00559 }
00560
00561 return metric;
00562 }
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573 public void setMetrics(BaseMetric []listOfMetrics) {
00574 int nbMetrics = listOfMetrics.length;
00575 this.arrStrMetrics = new String[nbMetrics];
00576 for(int i=0;i<nbMetrics;i++) {
00577 BaseMetric metric = listOfMetrics[i];
00578
00579 this.arrStrMetrics[i] = metric.getShortName() + ": "+ metric.getDisplayName();
00580 }
00581 }
00582
00583
00584
00585
00586
00587 public void setMetric( DerivedMetric metric ) {
00588 this.metric = metric;
00589 }
00590
00591
00592
00593
00594
00595 public DerivedMetric getMetric() {
00596 return metric;
00597 }
00598
00599
00603 public void okPressed() {
00604 if(this.checkExpression() && this.checkFormat()) {
00605
00606 doAction();
00607
00608
00609 objHistoryFormula.addLine( cbExpression.getText() );
00610 objHistoryName.addLine( cbName.getText() );
00611
00612 super.okPressed();
00613 }
00614 }
00615 }