View Javadoc

1   package org.whatsitcalled.webflange.engine;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.File;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.io.StringWriter;
8   import java.util.ArrayList;
9   import java.util.Calendar;
10  import java.util.List;
11  import java.util.Properties;
12  
13  import net.grinder.Grinder;
14  
15  import org.apache.commons.io.FileUtils;
16  import org.apache.log4j.Logger;
17  import org.apache.velocity.VelocityContext;
18  import org.apache.velocity.app.Velocity;
19  import org.apache.velocity.exception.MethodInvocationException;
20  import org.apache.velocity.exception.ParseErrorException;
21  import org.apache.velocity.exception.ResourceNotFoundException;
22  import org.whatsitcalled.spring.SpringContextUtil;
23  import org.whatsitcalled.webflange.ResourceFactory;
24  import org.whatsitcalled.webflange.file.FileManager;
25  import org.whatsitcalled.webflange.model.LoadTest;
26  import org.whatsitcalled.webflange.model.LoadTestRun;
27  import org.whatsitcalled.webflange.model.LoadTestRunDAO;
28  import org.whatsitcalled.webflange.model.LoadTestSummary;
29  import org.whatsitcalled.webflange.model.LoadTestSummaryDAO;
30  import org.whatsitcalled.webflange.service.LoadTestService;
31  
32  /*
33   * TODO use services not DAOs
34   */
35  public class GrinderTestRunner {
36  	private static final Logger LOGGER = Logger
37  			.getLogger(GrinderTestRunner.class);
38  
39  	private FileManager fileManager;
40  
41  	private TestScheduler scheduler;
42  
43  	private LoadTestSummaryDAO summaryDAO;
44  
45  	private LoadTestRunDAO runDAO;
46  
47  	private LoadTestService loadTestService;
48  
49  	private List<String> grinderJars;
50  
51  	private VelocityContext velocityContext;
52  
53  	public void init() {
54  		//TODO It would be great to have a module for context variables that has defaults configured via spring, but allows other vars to be set at a global level
55  		// Add the file path var for velocity
56  		velocityContext = new VelocityContext();
57  		String filePath = fileManager.getUploadFolder().getAbsolutePath();
58  		filePath = filePath.replaceAll("\\\\", "/");
59  		velocityContext.put("filePath", filePath);
60  		try {
61  			Velocity.init();
62  		} catch (Exception e) {
63  			LOGGER.error("Problem initializing Velocity", e);
64  		}
65  
66  	}
67  
68  	public void run(LoadTest loadTest) {
69  		loadTest.setRunning(true);
70  		loadTestService.saveLoadTest(loadTest);
71  		// Get the time
72  		long time = Calendar.getInstance().getTimeInMillis();
73  		saveRuntimeProperties(loadTest, loadTestService.getGrinderHostId(loadTest, time));
74  
75  		LOGGER.debug("Grinder CLASSPATH=" + System.getenv("CLASSPATH"));
76  		LOGGER.debug("Grinder JAVA_OPTS=" + System.getenv("JAVA_OPTS"));
77  
78  		String path = fileManager.getPropertyFile(loadTest).getAbsolutePath();
79  		String[] args = { path };
80  		try {
81  			LOGGER.debug("Executing test: " + loadTest.getName());
82  			Grinder.main(args);
83  			loadStats(loadTest.getId(), time);
84  			// GrinderReportGenerator.generateDefaultReport(loadTest, time);
85  		} catch (Exception e) {
86  			LOGGER.error("Execution of test: " + loadTest + " failed!", e);
87  		} finally {
88  			loadTest.setRunning(false);
89  			loadTestService.saveLoadTest(loadTest);
90  		}
91  
92  	}
93  
94  	public void saveRuntimeProperties(LoadTest loadTest, String grinderHostId) {
95  		// add the default properties
96  		String path = fileManager.getPropertyFile(loadTest).getAbsolutePath();
97  		File file = new File(path);
98  		java.util.Properties props = new Properties();
99  
100 		// Run the props through velocity
101 		StringWriter writer = new StringWriter();
102 		try {
103 			Velocity.evaluate(velocityContext, writer, "property-evaluator",
104 					loadTest.getProperties());
105 			String velOut = writer.toString();
106 			LOGGER.debug("Properties after Velocity:" + velOut);
107 			InputStream in = new ByteArrayInputStream(velOut.getBytes());
108 			props.load(in);
109 			props.putAll(scheduler.getProperties());
110 
111 			// convert the classpath
112 			String libFolder = SpringContextUtil.getWebApplicationContext()
113 					.getServletContext().getRealPath("/WEB-INF/lib");
114 			LOGGER.debug("libFolder=" + libFolder);
115 
116 			StringBuffer classpathBuf = new StringBuffer();
117 			for (String jar : grinderJars) {
118 				LOGGER.debug("Adding jar: " + jar + " to grinder classpath.");
119 				classpathBuf.append(libFolder);
120 				classpathBuf.append("/");
121 				classpathBuf.append(jar);
122 				classpathBuf.append(System.getProperty( "path.separator" ));
123 			}
124 			String classpath = classpathBuf.toString();
125 			LOGGER.debug("classpath=" + classpath);
126 			// Set other properties
127 			props.setProperty("grinder.jvm.classpath", classpath);
128 			props.setProperty("grinder.script", fileManager.getScriptFile(
129 					loadTest.getScript()).getAbsolutePath());
130 			props.setProperty("grinder.hostID", grinderHostId);
131 			props.setProperty("grinder.logDirectory", fileManager
132 					.getDataFolder().getAbsolutePath());
133 			props.setProperty("grinder.logProcessStreams", "false");
134 			props.store(FileUtils.openOutputStream(file), null);
135 
136 		} catch (ParseErrorException e) {
137 			LOGGER.error("Unable to load runtime properties", e);
138 		} catch (MethodInvocationException e) {
139 			LOGGER.error("Unable to load runtime properties", e);
140 		} catch (ResourceNotFoundException e) {
141 			LOGGER.error("Unable to load runtime properties", e);
142 		} catch (IOException e) {
143 			LOGGER.error("Unable to load runtime properties", e);
144 		}
145 	}
146 
147 	public List<String> getContextVars() {
148 		Object[] vars = velocityContext.getKeys();
149 		List<String> varList = new ArrayList<String>();
150 
151 		for (int i = 0; i < vars.length; i++) {
152 			String var = (String) vars[i];
153 			varList.add(var);
154 		}
155 		return varList;
156 	}
157 
158 	public static String[] splitLine(String line) {
159 
160 		String[] fields = new String[12];
161 		// First 11 fields are 14 chars long
162 		int beginIndex = 0;
163 		int endIndex = 13;
164 		for (int i = 0; i < 11; i++) {
165 			if (endIndex > line.length())
166 				endIndex = line.length();
167 			fields[i] = line.substring(beginIndex, endIndex);
168 			LOGGER.debug("fields[" + i + "]: " + fields[i]);
169 			fields[i] = fields[i].replaceAll("^\\s+", "");
170 			fields[i] = fields[i].replaceAll("\\s+$", "");
171 			if (fields[i].equals("?"))
172 				fields[i] = "0";
173 
174 			beginIndex = endIndex;
175 			endIndex = beginIndex + 13;
176 		}
177 
178 		fields[11] = line.substring(beginIndex);
179 
180 		return fields;
181 	}
182 
183 	public void loadStats(Long loadTestId, long time) {
184 
185 		// TODO put in service layer
186 		// Get the loadtest
187 		LoadTest test = loadTestService.getLoadTest(loadTestId);
188 
189 		// create and store the run and its associated LoadTest
190 		LoadTestRun run = new LoadTestRun(test, time);
191 
192 		// Store references to the Grinder files in the Run
193 		setRunFiles(run);
194 
195 		File file = loadTestService.getSummaryFile(run);
196 		List lines = null;
197 		try {
198 			lines = FileUtils.readLines(file);
199 		} catch (IOException e) {
200 			LOGGER.error("Trouble opening Grinder Summary File: "
201 					+ file.getAbsolutePath(), e);
202 		}
203 		String[] fields = new String[12];
204 		LoadTestSummary sum = null;
205 		for (Object line : lines) {
206 			String s = (String) line;
207 			fields = splitLine(s);
208 			if (!fields[0].equals("") && !fields[10].equals("")) {
209 				sum = new LoadTestSummary();
210 				sum.setTestName(fields[0]);
211 				sum.setTests(parseLong(fields[1]));
212 				sum.setErrors(parseLong(fields[2]));
213 				sum
214 						.setMeanTestTime(parseDouble(fields[3]));
215 				sum.setTestTimeStandardDeviation(parseFloat(fields[4]));
216 				sum.setMeanResponseLength(parseDouble(fields[5]));
217 				sum.setResponseBytesPerSecond(parseDouble(fields[6]));
218 				sum.setResponseErrors(parseLong(fields[7]));
219 				sum.setMeanTimeToResolveHost(parseDouble(fields[8]));
220 				sum.setMeanTimeToEstablishConnection(parseDouble(fields[9]));
221 				sum.setMeanTimeToFirstByte(parseDouble(fields[10]));
222 				sum.setTestUri(fields[11]);
223 				sum.setRun(run);
224 				LOGGER.debug("Adding Summary...\n" + sum.toString());
225 				if (sum.getTestName().equals("Totals")) {
226 					sum.setSummaryType(LoadTestSummary.TOTALS_SUMMARY_TYPE);
227 				}
228 				//summaryDAO.saveLoadTestSummary(sum);
229 				run.getSummaries().add(sum);
230 
231 			}
232 
233 		}
234 		test.getRuns().add(run);
235 		loadTestService.saveLoadTest(test);
236 	}
237 
238 	private long parseDouble(String sd) {
239 		if (!sd.matches("[0-9\\.]*")) return 0;
240 		long l = 0;
241 		try {
242 			l = Math.round(Double.parseDouble(sd));
243 		} catch (NumberFormatException e) {
244 			LOGGER.error(e);
245 		}
246 		return l;
247 	}
248 
249 	private long parseFloat(String sf) {
250 		long l = 0;
251 		try {
252 			l = Math.round(Float.parseFloat(sf));
253 		} catch (NumberFormatException e) {
254 			LOGGER.error(e);
255 		}
256 		return l;
257 	}
258 	
259 	private Long parseLong(String sd) {
260 		Long d = new Long(0);
261 		try {
262 			d = Long.parseLong(sd);
263 		} catch (NumberFormatException e) {
264 			LOGGER.error(e);
265 		}
266 
267 		return d;
268 	}
269 	private void setRunFiles(LoadTestRun run) {
270 		File summaryFile = loadTestService.getSummaryFile(run);
271 		if (summaryFile.exists())
272 			run.setSummaryFile(summaryFile.getName());
273 
274 		File dataFile = loadTestService.getDataFile(run);
275 		if (dataFile.exists())
276 			run.setDataFile(dataFile.getName());
277 
278 		File errorFile = loadTestService.getErrorFile(run);
279 		if (errorFile.exists())
280 			run.setErrorFile(errorFile.getName());
281 	}
282 
283 	public FileManager getFileManager() {
284 		return fileManager;
285 	}
286 
287 	public void setFileManager(FileManager fileManager) {
288 		this.fileManager = fileManager;
289 	}
290 
291 	public LoadTestRunDAO getRunDAO() {
292 		return runDAO;
293 	}
294 
295 	public void setRunDAO(LoadTestRunDAO runDAO) {
296 		this.runDAO = runDAO;
297 	}
298 
299 	public TestScheduler getScheduler() {
300 		return scheduler;
301 	}
302 
303 	public void setScheduler(TestScheduler scheduler) {
304 		this.scheduler = scheduler;
305 	}
306 
307 	public LoadTestSummaryDAO getSummaryDAO() {
308 		return summaryDAO;
309 	}
310 
311 	public void setSummaryDAO(LoadTestSummaryDAO summaryDAO) {
312 		this.summaryDAO = summaryDAO;
313 	}
314 
315 	public List getGrinderJars() {
316 		return grinderJars;
317 	}
318 
319 	public void setGrinderJars(List grinderJars) {
320 		this.grinderJars = grinderJars;
321 	}
322 
323 	public LoadTestService getLoadTestService() {
324 		return loadTestService;
325 	}
326 
327 	public void setLoadTestService(LoadTestService loadTestService) {
328 		this.loadTestService = loadTestService;
329 	}
330 
331 	public void runAsThread(LoadTest test) {
332 		// Create the object with the run() method
333 		Runnable runnable = new RunNow(test);
334 
335 		// Create the thread supplying it with the runnable object
336 		Thread thread = new Thread(runnable);
337 
338 		// Start the thread
339 		thread.start();
340 	}
341 
342 	class RunNow implements Runnable {
343 		private LoadTest loadTest;
344 
345 		RunNow(LoadTest test) {
346 			this.loadTest = test;
347 
348 		}
349 
350 		public void run() {
351 			ResourceFactory.getGrinderTestRunner().run(loadTest);
352 		}
353 	}
354 }