Java tutorial
/* * Copyright (C) 2013 salesforce.com, inc. * * 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 org.auraframework.test.perf; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.Date; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.auraframework.util.IOUtil; import org.auraframework.util.test.diff.PerfGoldFilesUtil; import org.auraframework.util.test.perf.metrics.PerfMetrics; import org.bson.Document; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.google.common.base.Charsets; import com.google.common.collect.Maps; import com.mongodb.MongoClient; import com.mongodb.MongoClientURI; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; /** * Utility methods related to the results generated by the perf runs. */ public final class PerfResultsUtil { private static final Logger LOG = Logger.getLogger(PerfResultsUtil.class.getSimpleName()); public static final File RESULTS_DIR; public static final Date RUN_TIME = new Date(); public static final String MONGO_URI = System.getProperty("mongoURI"); public static MongoClient MONGO_CLIENT; // TODO: Abstract this better private static MongoClient getMongoClient() { if (MONGO_CLIENT == null) { try { LOG.info("Trying to connect to MongoDB: " + MONGO_URI); MongoClientURI uri = new MongoClientURI(MONGO_URI); MONGO_CLIENT = new MongoClient(uri); } catch (Exception e) { LOG.info("Not able to connect to MongoDB"); return null; } } return MONGO_CLIENT; } public static void writeToDb(PerfMetrics metrics, String test) { try { MongoClient mongo = getMongoClient(); if (mongo != null) { MongoDatabase db = mongo.getDatabase("performance"); MongoCollection<Document> runs = db.getCollection("testRun"); JSONObject json = metrics.toJSONObject(); Document doc = Document.parse(json.toString()); doc.append("testName", test); doc.append("run", RUN_TIME); runs.insertOne(doc); } } catch (Exception e) { e.printStackTrace(); } } static { // use aura.perf.results.dir if set String resultsPath = System.getProperty("aura.perf.results.dir", null); // else use aura-integration-test/target/perf/results if running in aura if (resultsPath == null && new File("../aura-integration-test").exists()) { resultsPath = "../aura-integration-test/target/perf/results"; try { resultsPath = new File(resultsPath).getCanonicalPath(); } catch (IOException e) { LOG.log(Level.WARNING, "error canonicalizing " + resultsPath, e); } } // else target/perf/results if (resultsPath == null) { resultsPath = "target/perf/results"; } RESULTS_DIR = new File(resultsPath); LOG.info("perf results dir: " + RESULTS_DIR.getAbsolutePath()); RESULTS_DIR.mkdirs(); } public enum PerformanceMetrics { AURA_STATS("aurastats"), GOLD_FILES("goldfiles"), HEAPS("heaps", ".heapsnapshot"), PROFILES("profiles", ".cpuprofile"), TIMELINES("timelines"); private final String value; private final String fileExtension; private PerformanceMetrics(String value) { this(value, ".json"); } private PerformanceMetrics(String value, String fileExtension) { this.value = value; this.fileExtension = fileExtension; } public File getFile(String fileName) { File dir = new File(RESULTS_DIR, value); return new File(dir, fileName + fileExtension); } public static PerformanceMetrics getPerformanceMetricsFromType(String metricsType) { for (PerformanceMetrics result : PerformanceMetrics.values()) { if (result.value.equalsIgnoreCase(metricsType)) { return result; } } throw new IllegalArgumentException("unknown metricsType: " + metricsType); } } /** * @return the written file */ public static File writeGoldFile(PerfMetrics metrics, String fileName, boolean storeDetails) { File file = PerformanceMetrics.GOLD_FILES.getFile(fileName); RESULTS_JSON.addResultsFile(file); LOG.info("All artifacts for this perf test run have been written into file: " + file.toString()); try { ALL_GOLDFILES_JSON.addGoldfile(fileName, metrics); } catch (JSONException e) { LOG.log(Level.WARNING, "error generating _all.json", e); } return writeFile(file, PerfGoldFilesUtil.toGoldFileText(metrics, storeDetails), "goldfile"); } /** * @return the written file */ public static File writeAuraStats(String auraStatsContents, String fileName) { File file = PerformanceMetrics.AURA_STATS.getFile(fileName); RESULTS_JSON.addResultsFile(file); return writeFile(file, auraStatsContents, "Aura Stats"); } private static File writeFile(File file, String contents, String what) { OutputStreamWriter writer = null; try { IOUtil.mkdirs(file.getParentFile()); writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); writer.write(contents); LOG.info("wrote " + what + ": " + file.getAbsolutePath()); return file; } catch (Exception e) { LOG.log(Level.WARNING, "error writing " + file.getAbsolutePath(), e); return file; } finally { IOUtil.close(writer); } } /** * Writes the dev tools log for a perf test run to * System.getProperty("aura.perf.results.dir")/timelines/testName_timeline.json * * @return the written file */ public static File writeDevToolsLog(List<JSONObject> timeline, String fileName, String userAgent) { File file = PerformanceMetrics.TIMELINES.getFile(fileName); try { writeDevToolsLog(timeline, file, userAgent); RESULTS_JSON.addResultsFile(file); } catch (Exception e) { LOG.log(Level.WARNING, "error writing " + file.getAbsolutePath(), e); } return file; } private static void writeDevToolsLog(List<JSONObject> timeline, File file, String userAgent) throws Exception { BufferedWriter writer = null; try { file.getParentFile().mkdirs(); writer = new BufferedWriter(new FileWriter(file)); writer.write('['); writer.write(JSONObject.quote(userAgent)); for (JSONObject entry : timeline) { writer.write(','); writer.newLine(); writer.write(entry.toString()); } writer.write("]"); writer.newLine(); LOG.info("wrote dev tools log: " + file.getAbsolutePath()); } finally { IOUtil.close(writer); } } /** * Writes the JavaScript CPU profile data for a perf test run to * System.getProperty("aura.perf.results.dir")/profiles/testName_profile.cpuprofile * * @return the written file */ public static File writeJSProfilerData(Map<String, ?> jsProfilerData, String fileName) { File file = PerformanceMetrics.PROFILES.getFile(fileName); try { file.getParentFile().mkdirs(); BufferedWriter writer = null; try { FileOutputStream out = new FileOutputStream(file); writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.US_ASCII)); writer.write(new JSONObject(jsProfilerData).toString()); RESULTS_JSON.addResultsFile(file); LOG.info("wrote JavaScript CPU profile: " + file.getAbsolutePath()); } finally { IOUtil.close(writer); } } catch (Exception e) { LOG.log(Level.WARNING, "error writing " + file.getAbsolutePath(), e); } return file; } // JS heap snapshot /** * Writes the heap snapshot into a file, this file can be loaded into chrome dev tools -> Profiles -> Load * * @return the written file */ @SuppressWarnings("unchecked") public static File writeHeapSnapshot(Map<String, ?> data, String fileName) throws Exception { File file = PerformanceMetrics.HEAPS.getFile(fileName); BufferedWriter writer = null; try { file.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(file); // write using same format as CDT Save: // https://developers.google.com/chrome-developer-tools/docs/heap-profiling writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.US_ASCII)); writer.write('{'); writer.write(JSONObject.quote("snapshot")); writer.write(':'); new JSONObject((Map<String, ?>) data.get("snapshot")).write(writer); writer.write(','); writer.newLine(); writeList(writer, "nodes", (List<?>) data.get("nodes"), 5, false); writeList(writer, "edges", (List<?>) data.get("edges"), 3, false); writeList(writer, "trace_function_infos", (List<?>) data.get("trace_function_infos"), 1, false); writeList(writer, "trace_tree", (List<?>) data.get("trace_tree"), 1, false); writeList(writer, "strings", (List<?>) data.get("strings"), 1, true); writer.write('}'); RESULTS_JSON.addResultsFile(file); LOG.info("wrote heap snapshot: " + file.getAbsolutePath()); } finally { IOUtil.close(writer); } return file; } static void writeList(BufferedWriter writer, String key, List<?> list, int numPerLine, boolean last) throws IOException { writer.write(JSONObject.quote(key)); writer.write(':'); writer.write('['); for (int i = 0; i < list.size(); i++) { Object entry = list.get(i); if (i > 0) { if (numPerLine > 1) { if (i % numPerLine == 0) { writer.newLine(); } writer.write(','); } else { writer.write(','); writer.newLine(); } } if (entry instanceof String) { writer.write(JSONObject.quote((String) entry)); } else { writer.write(entry.toString()); } } if (numPerLine > 1) { writer.newLine(); } writer.write("]"); if (!last) { writer.write(','); writer.newLine(); } } // generate Results.json: public static final ResultsJSON RESULTS_JSON = new ResultsJSON(true); /** * Generates a Results.json file pointing to all the artifacts generated in a perf test run. */ public static final class ResultsJSON { private final JSONObject json = new JSONObject(); private int numResultFilesAdded; ResultsJSON(boolean writeOnJVMExit) { try { json.put("results", new JSONObject()); JSONObject build = new JSONObject(); json.put("build", build); addBuildInfo(build, "jenkins_build_number", "BUILD_NUMBER"); addBuildInfo(build, "jenkins_build_id", "BUILD_ID"); addBuildInfo(build, "git_branch", "GIT_BRANCH", "CURRENT_GIT_BRANCH"); addBuildInfo(build, "git_commit", "GIT_COMMIT", "CURRENT_GIT_COMMIT"); addBuildInfo(build, "aura_version", "AURA_VERSION"); addBuildInfo(build, "author_email", "AUTHOR_EMAIL"); addBuildInfo(build, "changelists", "CHANGELISTS"); } catch (Exception e) { LOG.log(Level.WARNING, "error adding build info", e); } if (writeOnJVMExit) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (numResultFilesAdded > 0) { File file = new File(RESULTS_DIR + "/Results.json"); writeFile(file, json.toString(), "Results.json"); } } }); } } void addResultsFile(File file) { numResultFilesAdded++; // i.e. timelines: { ui: { list: [..., ..., ...] }} try { JSONArray list = getListParent(file).getJSONArray("list"); // put filenames sorted in the JSONArray String fileName = file.getName(); int insertIndex = list.length(); for (int i = 0; i < list.length(); i++) { if (fileName.compareTo(list.getString(i)) < 0) { insertIndex = i; break; } } for (int i = list.length() - 1; i >= insertIndex; i--) { list.put(i + 1, list.get(i)); } list.put(insertIndex, fileName); } catch (Exception e) { LOG.log(Level.WARNING, "error adding results file: " + file, e); } } public void removeResultsFile(File file) { try { JSONObject parent = getListParent(file); JSONArray list = parent.getJSONArray("list"); JSONArray trimmedList = new JSONArray(); String fileName = file.getName(); for (int i = 0; i < list.length(); i++) { String value = list.getString(i); if (!fileName.equals(value)) { trimmedList.put(value); } } parent.put("list", trimmedList); } catch (Exception e) { LOG.log(Level.WARNING, "error removing results file: " + file, e); } } JSONObject getJSON() { return json; } private JSONObject getListParent(File file) throws JSONException { String relativePath = file.getParentFile().getPath(); relativePath = relativePath.substring(RESULTS_DIR.getPath().length() + 1); String[] folders = relativePath.split("/"); JSONObject parent = json.getJSONObject("results"); for (String folder : folders) { if (!parent.has(folder)) { parent.put(folder, new JSONObject()); } parent = parent.getJSONObject(folder); } if (!parent.has("list")) { parent.put("list", new JSONArray()); } return parent; } private static void addBuildInfo(JSONObject build, String key, String... envvars) throws JSONException { for (String envvar : envvars) { String value = System.getenv(envvar); if (value != null && value.trim().length() > 0) { build.put(key, value); return; // uses first non-null } } } } // write a _all.json for each namespace private static final AllGoldfilesJSON ALL_GOLDFILES_JSON = new AllGoldfilesJSON(true); /** * Writes a single _all.json containing all the goldfiles in a namespace */ private static final class AllGoldfilesJSON { private final Map<String, JSONObject> namespaceToAllJson = Maps.newHashMap(); AllGoldfilesJSON(boolean writeOnJVMExit) { if (writeOnJVMExit) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (namespaceToAllJson.size() > 0) { // write the goldfile inside each namespace for (String namespace : namespaceToAllJson.keySet()) { File file = new File(RESULTS_DIR + "/goldfiles/" + namespace + "/_all.json"); writeFile(file, namespaceToAllJson.get(namespace).toString(), namespace + "/_all.json"); } } } }); } } void addGoldfile(String fileName, PerfMetrics metrics) throws JSONException { int index = fileName.lastIndexOf('/'); String namespace = (index != -1) ? fileName.substring(0, index) : ""; String componentName = fileName.substring(index + 1); if (!namespaceToAllJson.containsKey(namespace)) { namespaceToAllJson.put(namespace, new JSONObject()); } JSONObject allJson = namespaceToAllJson.get(namespace); allJson.put(componentName, metrics.toJSONArrayWithoutDetails()); } } }