| /* |
| * Copyright 2008 Google 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 com.google.gwt.benchmarks.viewer.server; |
| |
| import com.google.gwt.benchmarks.client.Benchmark; |
| import com.google.gwt.benchmarks.viewer.client.Report; |
| import com.google.gwt.benchmarks.viewer.client.ReportSummary; |
| |
| import org.w3c.dom.Document; |
| |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| /** |
| * Serves up benchmark reports created during JUnit execution. |
| * |
| * The benchmark reports are read from the path specified by the system property |
| * named <code>Benchmark.REPORT_PATH</code>. In the case the property is not |
| * set, they are read from the user's current working directory. |
| */ |
| public class ReportDatabase { |
| |
| /** |
| * Indicates that a supplied path was invalid. |
| * |
| */ |
| public static class BadPathException extends RuntimeException { |
| String path; |
| |
| public BadPathException(String path) { |
| super("The path " + path + " does not exist."); |
| this.path = path; |
| } |
| |
| public String getPath() { |
| return path; |
| } |
| } |
| |
| private static class ReportEntry { |
| private long lastModified; |
| private Report report; |
| private ReportSummary summary; |
| |
| public ReportEntry(Report report, ReportSummary summary, long lastModified) { |
| this.report = report; |
| this.summary = summary; |
| this.lastModified = lastModified; |
| } |
| } |
| |
| private static class ReportFile { |
| File file; |
| long lastModified; |
| |
| ReportFile(File f) { |
| this.file = f; |
| this.lastModified = f.lastModified(); |
| } |
| } |
| |
| private static ReportDatabase database = new ReportDatabase(); |
| |
| /** |
| * The amount of time to go between report updates. |
| */ |
| private static final int UPDATE_DURATION_MILLIS = 30000; |
| |
| public static ReportDatabase getInstance() { |
| return database; |
| } |
| |
| private static String getReportId(File f) { |
| return f.getName(); |
| } |
| |
| /** |
| * The last time we updated our reports. |
| */ |
| private long lastUpdateMillis = -1L; |
| |
| /** |
| * The path to read benchmark reports from. |
| */ |
| private final String reportPath; |
| |
| /** |
| * A list of all reports by id. |
| */ |
| private Map<String, ReportEntry> reports = new HashMap<String, ReportEntry>(); |
| |
| /** |
| * Lock for reports. |
| */ |
| private Object reportsLock = new Object(); |
| |
| /** |
| * Lock for updating from file system. (Guarantees a single update while not |
| * holding reportsLock open). |
| */ |
| private Object updateLock = new Object(); |
| |
| /** |
| * Are we currently undergoing updating? |
| */ |
| private boolean updating = false; |
| |
| private ReportDatabase() throws BadPathException { |
| String path = System.getProperty(Benchmark.REPORT_PATH); |
| if (path == null || path.trim().equals("")) { |
| path = System.getProperty("user.dir"); |
| } |
| reportPath = path; |
| |
| if (!new File(reportPath).exists()) { |
| throw new BadPathException(reportPath); |
| } |
| } |
| |
| public Report getReport(String reportId) { |
| synchronized (reportsLock) { |
| ReportEntry entry = reports.get(reportId); |
| return entry == null ? null : entry.report; |
| } |
| } |
| |
| public List<ReportSummary> getReportSummaries() { |
| |
| /** |
| * There are probably ways to make this faster, but I've taken basic |
| * precautions to try to make this scale ok with multiple clients. |
| */ |
| |
| boolean update = false; |
| |
| // See if we need to do an update |
| // Go ahead and let others continue reading, even if an update is required. |
| synchronized (updateLock) { |
| if (!updating) { |
| long currentTime = System.currentTimeMillis(); |
| |
| if (currentTime > lastUpdateMillis + UPDATE_DURATION_MILLIS) { |
| update = updating = true; |
| } |
| } |
| } |
| |
| if (update) { |
| updateReports(); |
| } |
| |
| synchronized (reportsLock) { |
| List<ReportSummary> summaries = new ArrayList<ReportSummary>(reports.size()); |
| for (ReportEntry entry : reports.values()) { |
| summaries.add(entry.summary); |
| } |
| return summaries; |
| } |
| } |
| |
| private void updateReports() { |
| |
| File path = new File(reportPath); |
| |
| File[] files = path.listFiles(new FilenameFilter() { |
| public boolean accept(File f, String name) { |
| return name.startsWith("report-") && name.endsWith(".xml"); |
| } |
| }); |
| |
| Map<String, ReportEntry> filesToUpdate = new HashMap<String, ReportEntry>(); |
| Map<String, ReportFile> filesById = new HashMap<String, ReportFile>(); |
| for (int i = 0; i < files.length; ++i) { |
| File f = files[i]; |
| filesById.put(getReportId(f), new ReportFile(f)); |
| } |
| |
| // Lock temporarily so we can determine what needs updating |
| // (This could be a read-lock - not a general read-write lock, |
| // if we moved dead report removal outside of this critical section). |
| synchronized (reportsLock) { |
| |
| // Add reports which need to be updated or are new |
| for (int i = 0; i < files.length; ++i) { |
| File file = files[i]; |
| String reportId = getReportId(file); |
| ReportEntry entry = reports.get(reportId); |
| if (entry == null || entry.lastModified < file.lastModified()) { |
| filesToUpdate.put(reportId, null); |
| } |
| } |
| |
| // Remove reports which no longer exist |
| for (Iterator<String> it = reports.keySet().iterator(); it.hasNext();) { |
| if (filesById.get(it.next()) == null) { |
| it.remove(); |
| } |
| } |
| } |
| |
| try { |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| factory.setIgnoringElementContentWhitespace(true); |
| factory.setIgnoringComments(true); |
| DocumentBuilder builder = factory.newDocumentBuilder(); |
| |
| for (String id : filesToUpdate.keySet()) { |
| ReportFile reportFile = filesById.get(id); |
| String filePath = reportFile.file.getAbsolutePath(); |
| Document doc = builder.parse(filePath); |
| Report report = ReportXml.fromXml(doc.getDocumentElement()); |
| report.setId(id); |
| ReportSummary summary = report.getSummary(); |
| long lastModified = new File(filePath).lastModified(); |
| filesToUpdate.put(id, new ReportEntry(report, summary, lastModified)); |
| } |
| |
| // Update the reports |
| synchronized (reportsLock) { |
| for (String id : filesToUpdate.keySet()) { |
| reports.put(id, filesToUpdate.get(id)); |
| } |
| } |
| } catch (Exception e) { |
| // Even if we got an error, we'll just try again on the next update |
| // This might happen if a report has only been partially written, for |
| // example. |
| e.printStackTrace(); |
| } |
| |
| synchronized (updateLock) { |
| updating = false; |
| lastUpdateMillis = System.currentTimeMillis(); |
| } |
| } |
| } |