Attachment 'JMeter_MysqlCollector.patch'

Download

   1 Index: src/components/org/apache/jmeter/reporters/MysqlCollector.java
   2 ===================================================================
   3 --- src/components/org/apache/jmeter/reporters/MysqlCollector.java	(revision 0)
   4 +++ src/components/org/apache/jmeter/reporters/MysqlCollector.java	(revision 0)
   5 @@ -0,0 +1,309 @@
   6 +package org.apache.jmeter.reporters;
   7 +
   8 +import java.io.Serializable;
   9 +import java.sql.DriverManager;
  10 +import java.sql.SQLException;
  11 +import java.sql.Connection;
  12 +
  13 +import org.apache.jmeter.samplers.SampleEvent;
  14 +import org.apache.jmeter.samplers.SampleListener;
  15 +import org.apache.jmeter.samplers.SampleResult;
  16 +import org.apache.jmeter.testelement.AbstractTestElement;
  17 +import org.apache.jorphan.logging.LoggingManager;
  18 +import org.apache.log.Logger;
  19 +
  20 +import java.sql.Statement;
  21 +
  22 +/**
  23 + * 
  24 + * @author Brett Cave <brettcave@gmail.com>
  25 + * 
  26 + * Save responseData to a MySQL database.
  27 + */
  28 +public class MysqlCollector extends AbstractTestElement implements
  29 +		Serializable, SampleListener {
  30 +
  31 +	/**
  32 +	 * Variables.
  33 +	 */
  34 +	private static final long serialVersionUID = 3127617254347460877L;
  35 +
  36 +	private static final Logger log = LoggingManager.getLoggerForClass();
  37 +
  38 +	public static final String MYSQLHOST = "Mysql.host";
  39 +	public static final String MYSQLPORT = "Mysql.port";
  40 +	public static final String MYSQLDB = "Mysql.db";
  41 +	public static final String MYSQLTABLE = "Mysql.table";
  42 +	public static final String MYSQLUSER = "Mysql.user";
  43 +	public static final String MYSQLPASS = "Mysql.pass";
  44 +	public static final String MYSQLCREATETABLE = "Mysql.createtable";
  45 +	public static final String ERRORS_ONLY = "Mysql.errorsonly";
  46 +	public static final String SUCCESS_ONLY = "Mysql.successonly";
  47 +
  48 +	/**
  49 +	 * Default constructor. Nothing special.
  50 +	 */
  51 +	//TODO: The database should be initialized here, instead of after each sample.
  52 +	public MysqlCollector() {
  53 +		super();
  54 +	}
  55 +
  56 +	
  57 +	public MysqlCollector(String name) {
  58 +		this();
  59 +		setName(name);
  60 +	}
  61 +
  62 +	public void clear() {
  63 +		super.clear();
  64 +		synchronized (this) {
  65 +			// reset connection?
  66 +		}
  67 +	}
  68 +
  69 +	/**
  70 +	 * Required: this passes result to the processor.
  71 +	 */
  72 +	public void sampleOccurred(SampleEvent e) {
  73 +		processSample(e.getResult(), new Counter());
  74 +	}
  75 +
  76 +	/**
  77 +	 * 
  78 +	 * @param s SampleResult - The result of the sample
  79 +	 * @param c Counter - The number of samples
  80 +	 * 
  81 +	 * Sends the sample to be written to the database and increments counter, with a recursive function for sub-result arrays.
  82 +	 */
  83 +	private void processSample(SampleResult s, Counter c) {
  84 +		writeSampleToDb(s, c.num++);
  85 +		SampleResult[] sr = s.getSubResults();
  86 +		for (int i = 0; i < sr.length; i++) {
  87 +			processSample(sr[i], c);
  88 +		}
  89 +	}
  90 +
  91 +	/**
  92 +	 * 
  93 +	 * @param s SampleResult - the result of the sample that will be written to the database
  94 +	 * @param num - the sample number.
  95 +	 * 
  96 +	 * Writes the sample result data to the configured database.
  97 +	 */
  98 +	//TODO: It is very expensive to set up and tear down mysql connections, should the connect / close go elsewherE?
  99 +	private void writeSampleToDb(SampleResult s, int num) {
 100 +		if (s.isSuccessful()) {
 101 +			if (getErrorsOnly()) {
 102 +				return;
 103 +			}
 104 +		} else {
 105 +			if (getSuccessOnly()) {
 106 +				return;
 107 +			}
 108 +		}
 109 +		
 110 +		//TODO: Add an enum of database types and map to sampleResult types.
 111 +		/* 
 112 +		 * SampleResult getters that have relevant data.
 113 +		 * 
 114 +		 * getBytes(): int 
 115 +		 * getContentType(): varchar
 116 +		 * getDataType(): varchar
 117 +		 * getEndTime(): long
 118 +		 * getErrorCount(): int
 119 +		 * getGroupThreads(): int
 120 +		 * getIdleTime(): long
 121 +		 * getLatency(): long
 122 +		 * getMediaType(): varchar
 123 +		 * getParent(): varchar. reports need to take this into consideration.
 124 +		 * getRequestHeaders(): varchar
 125 +		 * getResponseCode(): varchar
 126 +		 * getResponseDataAsString(): text/blob
 127 +		 * getResponseHeaders(): varchar
 128 +		 * getResponseMessage(): varchar
 129 +		 * getSampleCount(): int
 130 +		 * getSampleLabel(): varchar
 131 +		 * getSamplerData(): text?
 132 +		 * getStartTime(): log
 133 +		 * getThreadName(): varchar
 134 +		 * getTime(): long
 135 +		 * getTimeStamp(): long
 136 +		 * getURL(): varchar
 137 +		 * getUrlAsString(): varchar
 138 +		 * isSuccessful(): boolean.
 139 +		 */
 140 +		
 141 +		// no optimization. at least a PK or index so performance doesn't totally suck.
 142 +		String dbSchema = "byteCount int, contentType varchar(200), dataType varchar(200), mediaType varchar(200)," +
 143 +				"timeStamp long, startTime long, endTime long, idleTime long, totalTime long, latency long," +
 144 +				"errorCount int, groupThreads int, parent varchar(200), requestHeaders varchar(200), repsonseCode varchar(200)," +
 145 +				"responseData text, responseHeaders text, responseMessage text," +
 146 +				"sampleCount int, sampleLabel text, samplerData text," +
 147 +				"threadName text, url text, success tinyint(1)";
 148 +		
 149 +		Connection con = connectDatabase();		// This should throw an exception if fails. the try below should catch it.
 150 +		
 151 +		//TODO: investigate asynchronous database writes - this could be slow if database is on a slow network connection.
 152 +		
 153 +		// Is this good here?
 154 +		try {
 155 +			Statement stmt = con.createStatement();
 156 +			if (getMysqlCreateTable()) {
 157 +				// InnoDB is probably the best bet, can do FK's for parents, etc. Add dropdown to GUI to select Engine.
 158 +				stmt.execute("CREATE TABLE IF NOT EXISTS `" + getMysqlTable()
 159 +						+ "` ("+dbSchema+") ENGINE=InnoDB DEFAULT CHARSET utf8");
 160 +			}
 161 +			
 162 +			// This is where mapping would come in handy. This is long and tedious, could be done much better.
 163 +			String insertData = getSqlString(s.getBytes());
 164 +			insertData += ",'" + getSqlString(s.getContentType()) + "','" + getSqlString(s.getDataType());
 165 +			insertData += "','" + getSqlString(s.getMediaType());
 166 +			insertData += "'," + getSqlString(s.getTimeStamp()) + "," + getSqlString(s.getStartTime());
 167 +			insertData += "," + getSqlString(s.getEndTime()) + "," + getSqlString(s.getIdleTime());
 168 +			insertData += "," + getSqlString(s.getTime()) + "," + getSqlString(s.getLatency());
 169 +			insertData += ", " + getSqlString(s.getErrorCount());
 170 +			insertData += "," + getSqlString(s.getGroupThreads());
 171 +			//insertData += ",'" + getSqlString(s.getParent().toString());  parent is null...
 172 +			insertData += ",'";
 173 +			insertData += "','" + getSqlString(s.getRequestHeaders());
 174 +			insertData += "','" + getSqlString(s.getResponseCode());
 175 +			//insertData += "','" + getSqlString(s.getResponseDataAsString()); // HTML Needs to be escaped properly. is this necesary?
 176 +			insertData += "','";
 177 +			insertData += "','" + getSqlString(s.getRequestHeaders()) + "','" + getSqlString(s.getResponseMessage());
 178 +			insertData += "'," + getSqlString(s.getSampleCount()) + ",'" + getSqlString(s.getSampleLabel());
 179 +			insertData += "','" + getSqlString(s.getSamplerData()) + "','" + getSqlString(s.getThreadName());
 180 +			insertData += "','" + getSqlString(s.getUrlAsString()) + "'," + getSqlString(s.isSuccessful());
 181 +			
 182 +			
 183 +			
 184 +			String insertString = "INSERT INTO " + getMysqlTable() + " values (" + insertData + ")";
 185 +			log.error("INSERT STRING: " + insertString);
 186 +			// Bad - no column ordering. depends on schema created in specific order above.
 187 +			stmt.execute(insertString);
 188 +		}
 189 +		catch (SQLException e) {
 190 +			log.error("Cannot save sampleresult to database: " + e.getMessage());
 191 +		}
 192 +		closeDatabase(con);  // shouldn't throw, if close fails, it may be because connection is no longer valid.
 193 +		
 194 +	}
 195 +
 196 +	public void sampleStarted(SampleEvent e) {
 197 +		// not used
 198 +	}
 199 +
 200 +	public void sampleStopped(SampleEvent e) {
 201 +		// not used
 202 +	}
 203 +
 204 +	private String getMysqlHost() {
 205 +		return getPropertyAsString(MYSQLHOST);
 206 +	}
 207 +
 208 +	private String getMysqlPort() {
 209 +		return getPropertyAsString(MYSQLPORT);
 210 +	}
 211 +
 212 +	private String getMysqlDb() {
 213 +		return getPropertyAsString(MYSQLDB);
 214 +	}
 215 +
 216 +	private String getMysqlTable() {
 217 +		return getPropertyAsString(MYSQLTABLE);
 218 +	}
 219 +
 220 +	private boolean getMysqlCreateTable() {
 221 +		return getPropertyAsBoolean(MYSQLCREATETABLE);
 222 +	}
 223 +
 224 +	private String getMysqlUser() {
 225 +		return getPropertyAsString(MYSQLUSER);
 226 +	}
 227 +
 228 +	private String getMysqlPass() {
 229 +		return getPropertyAsString(MYSQLPASS);
 230 +	}
 231 +
 232 +	private boolean getErrorsOnly() {
 233 +		return getPropertyAsBoolean(ERRORS_ONLY);
 234 +	}
 235 +
 236 +	private boolean getSuccessOnly() {
 237 +		return getPropertyAsBoolean(SUCCESS_ONLY);
 238 +	}
 239 +	
 240 +	private String getSqlString(String unescaped) {
 241 +		try {
 242 +			return unescaped.replaceAll("'", "\\'");
 243 +		}
 244 +		catch (Exception e) {
 245 +			return "";
 246 +		}
 247 +	}
 248 +	
 249 +	private String getSqlString(int val) {
 250 +		try {
 251 +			return String.valueOf(val);
 252 +		}
 253 +		catch (Exception e) {
 254 +			return "";
 255 +		}
 256 +	}
 257 +	
 258 +	private String getSqlString(long val) {
 259 +		try {
 260 +			return String.valueOf(val);
 261 +		}
 262 +		catch (Exception e) {
 263 +			return "";
 264 +		}
 265 +	}
 266 +	
 267 +	private String getSqlString(boolean bool) {
 268 +		try {
 269 +			return String.valueOf(bool);
 270 +		}
 271 +		catch (Exception e) {
 272 +			return "";
 273 +		}
 274 +	}
 275 +	
 276 +	private Connection connectDatabase() {
 277 +		log.info("Connecting to database with URL 'jdbc:mysql://" + getMysqlHost() + ":" + getMysqlPort() + "/" + getMysqlDb() + 
 278 +				"' and user '" + getMysqlUser() + "'");
 279 +		Connection con = null;
 280 +		try {
 281 +			Class.forName("com.mysql.jdbc.Driver");
 282 +		} catch (ClassNotFoundException e) {
 283 +			log.error("Driver not on classpath (" + e.getMessage());
 284 +		}
 285 +		
 286 +		String url = "jdbc:mysql://" + getMysqlHost() + ":" + getMysqlPort()
 287 +				+ "/" + getMysqlDb();
 288 +		try {
 289 +			con = DriverManager.getConnection(url, getMysqlUser(),
 290 +					getMysqlPass());
 291 +			return con;
 292 +		}
 293 +		catch (SQLException e) {
 294 +			log.error("MysqlCollector ERROR: " + e.getMessage());
 295 +			return null;
 296 +		}
 297 +	}
 298 +	
 299 +	private void closeDatabase(Connection con) {
 300 +		try {
 301 +			//con.commit(); // "cant call commit if autocommit is true.".
 302 +			//This can be set up from GUI with "Autocommit?" option to enable or disable in driver if necessary.
 303 +			con.close();
 304 +		}
 305 +		catch (SQLException e) {
 306 +			log.error("Could not close connection: " + e.getMessage());
 307 +		}
 308 +	}
 309 +
 310 +	// How many samples have been received?
 311 +	private static class Counter {
 312 +		int num;
 313 +	}
 314 +}
 315 Index: src/components/org/apache/jmeter/reporters/gui/MysqlCollectorGui.java
 316 ===================================================================
 317 --- src/components/org/apache/jmeter/reporters/gui/MysqlCollectorGui.java	(revision 0)
 318 +++ src/components/org/apache/jmeter/reporters/gui/MysqlCollectorGui.java	(revision 0)
 319 @@ -0,0 +1,217 @@
 320 +package org.apache.jmeter.reporters.gui;
 321 +
 322 +import java.awt.BorderLayout;
 323 +import java.awt.GridBagConstraints;
 324 +import java.awt.GridBagLayout;
 325 +
 326 +import javax.swing.Box;
 327 +import javax.swing.JCheckBox;
 328 +import javax.swing.JLabel;
 329 +import javax.swing.JPanel;
 330 +import javax.swing.JPasswordField;
 331 +import javax.swing.JTextField;
 332 +
 333 +import org.apache.jmeter.reporters.MysqlCollector;
 334 +import org.apache.jmeter.samplers.Clearable;
 335 +import org.apache.jmeter.testelement.TestElement;
 336 +import org.apache.jmeter.visualizers.gui.AbstractListenerGui;
 337 +
 338 +
 339 +/**
 340 + * GUI for mysql collector. 
 341 + * 
 342 + * @author brett
 343 + * 
 344 + */
 345 +public class MysqlCollectorGui extends AbstractListenerGui implements Clearable {
 346 +
 347 +    /**
 348 +	 * 
 349 +	 */
 350 +	private static final long serialVersionUID = 2931300846585391445L;
 351 +
 352 +	private JTextField mysqlHost;
 353 +    
 354 +    private JTextField mysqlPort;
 355 +    
 356 +    private JTextField mysqlDb;
 357 +    
 358 +    private JTextField mysqlTable;
 359 +    
 360 +    private JCheckBox mysqlCreateTable;
 361 +    
 362 +    private JTextField mysqlUser;
 363 +    
 364 +    private JPasswordField mysqlPassword;
 365 +
 366 +    private JCheckBox errorsOnly;
 367 +
 368 +    private JCheckBox successOnly;
 369 +
 370 +
 371 +    public MysqlCollectorGui() {
 372 +        super();
 373 +        init();
 374 +    }
 375 +
 376 +    /**
 377 +     * @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel()
 378 +     */
 379 +    public String getLabelResource() {
 380 +        return "MySQL Collector";
 381 +    }
 382 +
 383 +    /**
 384 +     * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(TestElement)
 385 +     */
 386 +    public void configure(TestElement el) {
 387 +        super.configure(el);
 388 +        mysqlHost.setText(el.getPropertyAsString(MysqlCollector.MYSQLHOST));
 389 +        mysqlPort.setText(el.getPropertyAsString(MysqlCollector.MYSQLPORT));
 390 +        mysqlDb.setText(el.getPropertyAsString(MysqlCollector.MYSQLDB));
 391 +        mysqlTable.setText(el.getPropertyAsString(MysqlCollector.MYSQLTABLE));
 392 +        mysqlCreateTable.setSelected(el.getPropertyAsBoolean(MysqlCollector.MYSQLCREATETABLE));
 393 +        mysqlUser.setText(el.getPropertyAsString(MysqlCollector.MYSQLUSER));
 394 +        mysqlPassword.setText(el.getPropertyAsString(MysqlCollector.MYSQLPASS));
 395 +        errorsOnly.setSelected(el.getPropertyAsBoolean(MysqlCollector.ERRORS_ONLY));
 396 +        successOnly.setSelected(el.getPropertyAsBoolean(MysqlCollector.SUCCESS_ONLY));;
 397 +    }
 398 +
 399 +    /**
 400 +     * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()
 401 +     */
 402 +    public TestElement createTestElement() {
 403 +    	MysqlCollector mysqlSaver = new MysqlCollector();
 404 +        modifyTestElement(mysqlSaver);
 405 +        return mysqlSaver;
 406 +    }
 407 +
 408 +    /**
 409 +     * Modifies a given TestElement to mirror the data in the gui components.
 410 +     *
 411 +     * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement)
 412 +     */
 413 +    public void modifyTestElement(TestElement te) {
 414 +        super.configureTestElement(te);
 415 +        te.setProperty(MysqlCollector.MYSQLHOST, mysqlHost.getText());
 416 +        te.setProperty(MysqlCollector.MYSQLPORT, mysqlPort.getText());
 417 +        te.setProperty(MysqlCollector.MYSQLDB, mysqlDb.getText());
 418 +        te.setProperty(MysqlCollector.MYSQLUSER, mysqlUser.getText());
 419 +        te.setProperty(MysqlCollector.MYSQLTABLE, mysqlTable.getText());
 420 +        te.setProperty(MysqlCollector.MYSQLCREATETABLE, mysqlCreateTable.isSelected());
 421 +        te.setProperty(MysqlCollector.MYSQLPASS, String.valueOf(mysqlPassword.getPassword()));
 422 +        te.setProperty(MysqlCollector.ERRORS_ONLY, errorsOnly.isSelected());
 423 +        te.setProperty(MysqlCollector.SUCCESS_ONLY, successOnly.isSelected());
 424 +    }
 425 +
 426 +    /**
 427 +     * Implements JMeterGUIComponent.clearGui
 428 +     */
 429 +    public void clearGui() {
 430 +        super.clearGui();
 431 +        mysqlDb.setText("");
 432 +        mysqlHost.setText("");
 433 +        mysqlPort.setText("3306");
 434 +        mysqlTable.setText("");
 435 +        mysqlCreateTable.setSelected(true);
 436 +        mysqlUser.setText("");
 437 +        mysqlPassword.setText("");
 438 +        errorsOnly.setSelected(false);
 439 +        successOnly.setSelected(false);
 440 +    }
 441 +
 442 +    private void init() {
 443 +        setLayout(new BorderLayout());
 444 +        setBorder(makeBorder());
 445 +        Box box = Box.createVerticalBox();
 446 +        box.add(makeTitlePanel());
 447 +        box.add(createServerDetailsPanel());
 448 +        
 449 +        mysqlCreateTable = new JCheckBox("Create table?");
 450 +        box.add(mysqlCreateTable);
 451 +        errorsOnly = new JCheckBox("Only log errors?");
 452 +        box.add(errorsOnly);
 453 +        successOnly = new JCheckBox("Only log success?");
 454 +        box.add(successOnly);
 455 +        add(box, BorderLayout.NORTH);
 456 +    }
 457 +
 458 +    private JPanel createServerDetailsPanel()
 459 +    {
 460 +        JLabel hostLabel, portLabel, dbLabel, tableLabel, userLabel, passwordLabel;
 461 +        
 462 +        mysqlHost = new JTextField(10);
 463 +        hostLabel = new JLabel("Host:Port");
 464 +        hostLabel.setLabelFor(mysqlHost);
 465 +        
 466 +        mysqlPort = new JTextField(4);
 467 +        mysqlPort.setText("3306");
 468 +        portLabel = new JLabel(":");
 469 +        portLabel.setLabelFor(mysqlPort);
 470 +        
 471 +        mysqlDb = new JTextField(10);
 472 +        dbLabel = new JLabel("Database/Table");
 473 +        dbLabel.setLabelFor(mysqlDb);
 474 +        
 475 +        mysqlTable = new JTextField(10);
 476 +        tableLabel = new JLabel("/");
 477 +        tableLabel.setLabelFor(mysqlTable);
 478 +        
 479 +        mysqlUser = new JTextField(10);
 480 +        userLabel = new JLabel("User/Password");
 481 +        userLabel.setLabelFor(mysqlUser);
 482 +        
 483 +        mysqlPassword = new JPasswordField(10);
 484 +        passwordLabel = new JLabel("/");
 485 +        passwordLabel.setLabelFor(mysqlPassword);
 486 +        
 487 +        JPanel serverDetailsPanel = new JPanel(new GridBagLayout());
 488 +        
 489 +        GridBagConstraints c = new GridBagConstraints();
 490 +        int gridxinc = 5, gridyinc = 5;
 491 +        c.ipady = 2;
 492 +        c.ipadx = 2;
 493 +        
 494 +        c.gridx = 0;
 495 +        c.gridy = 0;
 496 +        c.anchor = GridBagConstraints.WEST;
 497 +        
 498 +        serverDetailsPanel.add(hostLabel, c);
 499 +        c.gridx += gridxinc;
 500 +        serverDetailsPanel.add(mysqlHost,c);
 501 +        c.gridx += gridxinc;
 502 +        serverDetailsPanel.add(portLabel,c);
 503 +        c.gridx += gridxinc;
 504 +        serverDetailsPanel.add(mysqlPort,c);
 505 +        
 506 +        c.gridy += gridyinc;  // nl
 507 +        c.gridx = 0; // cr
 508 +        
 509 +        serverDetailsPanel.add(dbLabel,c);
 510 +        c.gridx += gridxinc;
 511 +        serverDetailsPanel.add(mysqlDb,c);
 512 +        c.gridx += gridxinc;
 513 +        serverDetailsPanel.add(tableLabel,c);
 514 +        c.gridx += gridxinc;
 515 +        serverDetailsPanel.add(mysqlTable,c);
 516 +        
 517 +        c.gridy += gridyinc;
 518 +        c.gridx = 0;
 519 +        
 520 +        serverDetailsPanel.add(userLabel,c);
 521 +        c.gridx += gridxinc;
 522 +        serverDetailsPanel.add(mysqlUser,c);
 523 +        c.gridx += gridxinc;
 524 +        serverDetailsPanel.add(passwordLabel,c);
 525 +        c.gridx += gridxinc;
 526 +        serverDetailsPanel.add(mysqlPassword,c);
 527 +        c.gridy += gridyinc;
 528 +        
 529 +        return serverDetailsPanel;
 530 +    }
 531 +
 532 +    // Needed to avoid Class cast error in Clear.java
 533 +    public void clearData() {
 534 +    }
 535 +
 536 +}
 537 

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.

You are not allowed to attach a file to this page.