/** * Copyright 2008 The University of North Carolina at Chapel Hill * * 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 edu.unc.lib.dl.admin.controller; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @Controller public class PerformanceMonitorController { private final long MILLISECONDS_IN_ONE_HOUR = 60 * 60 * 1000; private static final Logger log = LoggerFactory .getLogger(PerformanceMonitorController.class); private static final String NEW_LINE_SEPARATOR = "\n"; private static final Object [] FILE_HEADERS = { "date", "uuid", "throughput_files", "throughput_bytes", "queued_duration", "ingest_duration", "finished", "moves", "image_enh", "failed_image_enh", "metadata_enh", "failed_metadata_enh", "solr_enh", "failed_solr_enh", "fulltext_enh", "failed_fulltext_enh", "thumbnail_enh", "failed_thumbnail_enh", "failed_deposit", "failed_deposit_job" }; private static final String[] MOVES_ENHANCEMENTS_JOBS_ARRAY = { "moves", "finished-enh:edu.unc.lib.dl.cdr.services.imaging.ImageEnhancementService", "failed-enh:edu.unc.lib.dl.cdr.services.imaging.ImageEnhancementService", "finished-enh:edu.unc.lib.dl.cdr.services.techmd.TechnicalMetadataEnhancementService", "failed-enh:edu.unc.lib.dl.cdr.services.techmd.TechnicalMetadataEnhancementService", "finished-enh:edu.unc.lib.dl.cdr.services.solr.SolrUpdateEnhancementService", "failed-enh:edu.unc.lib.dl.cdr.services.solr.SolrUpdateEnhancementService", "finished-enh:edu.unc.lib.dl.cdr.services.text.FullTextEnhancementService", "failed-enh:edu.unc.lib.dl.cdr.services.text.FullTextEnhancementService", "finished-enh:edu.unc.lib.dl.cdr.services.imaging.ThumbnailEnhancementService", "failed-enh:edu.unc.lib.dl.cdr.services.imaging.ThumbnailEnhancementService" }; private CSVFormat csvFileFormat = CSVFormat.DEFAULT.withRecordSeparator(NEW_LINE_SEPARATOR); @Autowired private String dataPath; @Autowired private JedisPool jedisPool; public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } public Set<String> getDepositMetrics() { try (Jedis jedis = getJedisPool().getResource()){ return jedis.keys("deposit-metrics:*"); } } private Boolean buildFile(String path) { File csvFile = new File(path); if (csvFile.exists() && !csvFile.isDirectory()) { Long currentTime = System.currentTimeMillis(); Long fileCreationTime = csvFile.lastModified(); if ((currentTime - fileCreationTime ) > MILLISECONDS_IN_ONE_HOUR) { return true; } return false; } else if (!csvFile.exists()) { return true; } else { return false; } } /** * Gets totals for metrics that are only aggregated daily such as moves & enhancements * It also grabs throughput totals for older data, before these fields were set to measure performance at the individual deposit level * @return */ public String getOperationsData() { String filePath = dataPath + "ingest-times-daily.csv"; if (buildFile(filePath)) { Set<String> deposits = getDepositMetrics(); try (Jedis jedis = getJedisPool().getResource()) { FileWriter fileWriter = null; Set<String> operations = null; Map<String, String> depositJob = null; Map<String, String> operationJob = null; String[] depositKeys = null; operations = jedis.keys("operation-metrics:*"); fileWriter = new FileWriter(filePath); try (CSVPrinter csvFilePrinter = new CSVPrinter(fileWriter, csvFileFormat)) { csvFilePrinter.printRecord(FILE_HEADERS); Boolean matchingDate = false; for (String deposit : deposits) { depositKeys = deposit.split(":"); // Ignore data for individual deposits by uuid. Only need the daily ones in this instance if (depositKeys.length > 2) { continue; } depositJob = jedis.hgetAll(deposit); String jobDate = depositKeys[1]; String throughputFiles = depositJob.get("throughput-files"); String throughputBytes = depositJob.get("throughput-bytes"); String finished = depositJob.get("finished"); String failed = depositJob.get("failed"); String failedDepositJob = depositJob.get("failed-job:edu.unc.lib.dl.cdr.services.techmd.TechnicalMetadataEnhancementService"); for (String operation : operations) { String operationDate = operation.split(":")[1]; if (operationDate.equals(jobDate)) { operationJob = jedis.hgetAll(operation); List<String> data = new ArrayList<>(); data.add(jobDate); data.add("N/A"); data.add(throughputFiles); data.add(throughputBytes); data.add("0"); data.add("0"); data.add(finished); for (String field : MOVES_ENHANCEMENTS_JOBS_ARRAY) { String fieldValue = operationJob.get(field); data.add(fieldValue); } data.add(failed); data.add(failedDepositJob); csvFilePrinter.printRecord(data); matchingDate = true; break; } else { matchingDate = false; } } if (!matchingDate) { List<String> data = new ArrayList<>(); data.add(jobDate); data.add("N/A"); data.add(throughputFiles); data.add(throughputBytes); data.add("0"); data.add("0"); data.add(finished); this.addEmptyFields(data, MOVES_ENHANCEMENTS_JOBS_ARRAY); data.add(failed); data.add(failedDepositJob); csvFilePrinter.printRecord(data); } } } } catch (Exception e) { log.error("Failed to write data to {}", filePath, e); } } try { return FileUtils.readFileToString(new File(filePath)); } catch (IOException e) { log.error("Error unable to read file to string from filepath {}", filePath, e); return null; } } /** * Gets totals for metrics that happen on every deposit grouped by uuid * @return */ public String getDepositsData() { String filePath = dataPath + "ingest-times-daily-deposit.csv"; if (buildFile(filePath)) { try (Jedis jedis = getJedisPool().getResource()) { Set<String> deposits = getDepositMetrics(); FileWriter fileWriter = null; CSVPrinter csvFilePrinter = null; fileWriter = new FileWriter(filePath); csvFilePrinter = new CSVPrinter(fileWriter, csvFileFormat); csvFilePrinter.printRecord(FILE_HEADERS); for (String deposit : deposits) { String[] depositKeys = deposit.split(":"); // Ignore data for daily deposits. Only need the ones by uuid in this instance if (depositKeys.length < 3) { continue; } Map<String, String> depositJob = jedis.hgetAll(deposit); String jobDate = depositKeys[1]; String jobUUID = depositKeys[2]; String throughputFiles = depositJob.get("throughput-files"); String throughputBytes = depositJob.get("throughput-bytes"); String queuedDuration = depositJob.get("queued-duration"); String ingestDuration = depositJob.get("duration"); List<String> data = new ArrayList<String>(); data.add(jobDate); data.add(jobUUID); data.add(throughputFiles); data.add(throughputBytes); data.add(queuedDuration); data.add(ingestDuration); data.add("0"); this.addEmptyFields(data, MOVES_ENHANCEMENTS_JOBS_ARRAY); data.add("0"); data.add("0"); csvFilePrinter.printRecord(data); } csvFilePrinter.close(); } catch (Exception e) { log.error("Failed to write data to {}", filePath, e); } } try { return FileUtils.readFileToString(new File(filePath)); } catch (IOException e) { log.error("Error unable to read file to string from filepath {}", filePath, e); return null; } } /** * Add default value for fields that don't return anything * @param data * @param arrayValues * @return */ private List<String> addEmptyFields(List<String> data, String[] arrayValues) { int i = 0; while (i < arrayValues.length) { data.add("0"); i++; } return data; } @RequestMapping(value = "performanceMonitor", method = RequestMethod.GET) public String performanceMonitor() { return "report/performanceMonitor"; } @RequestMapping(value = "sendOperationsData", method = RequestMethod.GET) public @ResponseBody String sendOperationsData(HttpServletResponse response) { response.setContentType("text/plain; charset=utf-8"); return getOperationsData(); } @RequestMapping(value = "sendDepositsData", method = RequestMethod.GET) public @ResponseBody String sendDepositData(HttpServletResponse response) { response.setContentType("text/plain; charset=utf-8"); return getDepositsData(); } }