/** * */ package com.taobao.top.analysis.node.component; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.taobao.top.analysis.config.MasterConfig; import com.taobao.top.analysis.node.IJobExporter; import com.taobao.top.analysis.node.job.Job; import com.taobao.top.analysis.node.job.JobTask; import com.taobao.top.analysis.node.job.JobTaskResult; import com.taobao.top.analysis.node.operation.CreateReportOperation; import com.taobao.top.analysis.node.operation.JobDataOperation; import com.taobao.top.analysis.statistics.data.Report; import com.taobao.top.analysis.statistics.data.Rule; import com.taobao.top.analysis.util.AnalysisConstants; import com.taobao.top.analysis.util.NamedThreadFactory; import com.taobao.top.analysis.util.ReportUtil; /** * 默认报表输出实现 * * @author fangweng * @Email fangweng@taobao.com * 2011-11-25 * */ public class FileJobExporter implements IJobExporter { private static final Log logger = LogFactory.getLog(FileJobExporter.class); /** * 用于输出报表文件的线程池 */ private ThreadPoolExecutor createReportFileThreadPool; private MasterConfig config; private int maxCreateReportWorker = 8; private long lastRuntime=(System.currentTimeMillis() + 8 * 60 * 60 * 1000) / 86400000; public int getMaxCreateReportWorker() { return maxCreateReportWorker; } public void setMaxCreateReportWorker(int maxCreateReportWorker) { this.maxCreateReportWorker = maxCreateReportWorker; } @Override public MasterConfig getConfig() { return config; } @Override public void setConfig(MasterConfig config) { this.config = config; } @Override public void init() { if (this.config != null) maxCreateReportWorker = this.config.getMaxCreateReportWorker(); if(logger.isInfoEnabled()) logger.info("filejobExporter init end, maxCreateReportWorker size : " + maxCreateReportWorker); createReportFileThreadPool = new ThreadPoolExecutor( maxCreateReportWorker, maxCreateReportWorker, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory("createReportFile_worker")); } @Override public void releaseResource() { if (createReportFileThreadPool != null) createReportFileThreadPool.shutdown(); } @Override public List<String> exportReport(Job job, boolean needTimeSuffix) { try { long timer = System.currentTimeMillis(); List<String> result = exportReport(job.getStatisticsRule(), job.getJobConfig().getOutput(), job.getJobName(), needTimeSuffix, job.getJobResult(), job.getJobConfig().getOutputEncoding()); job.setJobExportTime(System.currentTimeMillis() - timer); return result; } catch (Throwable e) { logger.error(e); } return null; } @Override public List<String> exportReport(JobTask jobTask,JobTaskResult jobTaskResult,boolean needTimeSuffix) { return exportReport(jobTask.getStatisticsRule(),jobTask.getOutput() ,jobTask.getTaskId(),needTimeSuffix,jobTaskResult.getResults(),jobTask.getOutputEncoding()); } protected List<String> exportReport(Rule statisticsRule,String reportOutput,String id,boolean needTimeSuffix ,Map<String, Map<String, Object>> entryResultPool,String outputEncoding) { if (logger.isWarnEnabled()) logger.warn("start exportReport now, id : " + id + ", output : " + reportOutput); long start = System.currentTimeMillis(); List<String> reports = new CopyOnWriteArrayList<String>(); if (entryResultPool == null || statisticsRule.getReportPool() == null || (entryResultPool != null && entryResultPool.size() == 0) || (statisticsRule.getReportPool() != null && statisticsRule.getReportPool().size() == 0)) { logger.info("entryPool " + entryResultPool + "," + entryResultPool.size() + ";reportPool " + statisticsRule.getReportPool() + "," + statisticsRule.getReportPool().size()); return reports; } //清理lazy数据 ReportUtil.cleanLazyData(entryResultPool, statisticsRule.getEntryPool()); //做一下lazy处理,用于输出 ReportUtil.lazyMerge(entryResultPool, statisticsRule.getEntryPool()); if(logger.isInfoEnabled()) { logger.info("clean lazyData and lazyMerge export " + id ); } Calendar calendar = Calendar.getInstance(); String currentTime = new StringBuilder() .append(calendar.get(Calendar.YEAR)).append("-") .append(calendar.get(Calendar.MONTH) + 1).append("-") .append(calendar.get(Calendar.DAY_OF_MONTH)).toString(); calendar.add(Calendar.DAY_OF_MONTH, -1); String statTime = new StringBuilder() .append(calendar.get(Calendar.YEAR)).append("-") .append(calendar.get(Calendar.MONTH) + 1).append("-") .append(calendar.get(Calendar.DAY_OF_MONTH)).toString(); String rootDir = reportOutput; //去掉前缀,主要用于协议的前缀 if (rootDir.indexOf(":") > 0) rootDir = rootDir.substring(rootDir.indexOf(":") +1); if (!rootDir.endsWith(File.separator)) rootDir = new StringBuilder(rootDir).append(File.separator).toString(); if (config != null && StringUtils.isNotEmpty(config.getMasterName())) rootDir = new StringBuilder(rootDir).append(config.getMasterName()) .append(File.separator).append(id).append(File.separator).toString(); else rootDir = new StringBuilder(rootDir).append(id).append(File.separator).toString(); StringBuilder periodRootDir = new StringBuilder(); StringBuilder periodDir = new StringBuilder(); StringBuilder normalDir = new StringBuilder(); //优化,不再先删除日报表再写 //采用先写临时文件,写完后,重命名的方式 if (rootDir != null) { periodRootDir.append(rootDir).append("period").append(File.separator); periodDir.append(periodRootDir).append(currentTime).append(File.separator); normalDir = new StringBuilder(rootDir).append(currentTime).append(File.separator); File targetDir = new java.io.File(normalDir.toString()); File period = new java.io.File(periodDir.toString()); if (!period.exists() || (period.exists() && !period.isDirectory())) { period.mkdirs(); } if (!targetDir.exists() || (targetDir.exists() && !targetDir.isDirectory())) { targetDir.mkdirs(); } else { // 删除已有的所有的历史文件 if (targetDir.exists() && targetDir.isDirectory()) { File[] deleteFiles = targetDir.listFiles(); for (File f : deleteFiles) { if(f.getAbsolutePath().endsWith(".temp")) f.delete(); } } } } Iterator<Report> iter = statisticsRule.getReportPool() .values().iterator(); CountDownLatch countDownLatch = new CountDownLatch(statisticsRule.getReportPool().size()); List<String> reportFiles = new ArrayList<String>(); while (iter.hasNext()) { Report report = iter.next(); if(logger.isInfoEnabled()) { logger.info("check report need to generated by this master " + report.getId() ); } //判断是否是自己要输出的报表,在多个master情况下 if (statisticsRule.getReport2Master() != null && statisticsRule.getReport2Master().size() > 0 && config != null) { String r = statisticsRule.getReport2Master().get(report.getId()); if (r == null || !r.startsWith(ReportUtil.getIp()) || !r.endsWith(String.valueOf(config.getMasterPort()))) { countDownLatch.countDown(); continue; } } if(logger.isInfoEnabled()) { logger.info("report need to generated by this master " + report.getId() ); } String reportFile; String reportDir = normalDir.toString(); if(rootDir==null) reportDir = new StringBuilder(report.getFile()).append(File.separator).toString(); // 增加对于周期性输出的报表处理,就是根据FileName创建目录,目录中文件是FileName+时间戳. if (report.isPeriod()) { if(start-report.getLastExportTime()>report.getExportInterval()){ report.setLastExportTime(start); }else{ countDownLatch.countDown(); continue; } if(report.isAppend()){ reportDir = new StringBuilder().append(periodRootDir) .append(report.getFile()).append(File.separator) .toString(); }else{ reportDir = new StringBuilder().append(periodDir) .append(report.getFile()).append(File.separator) .toString(); } File tmpDir = new java.io.File(reportDir); if (!tmpDir.exists() || (tmpDir.exists() && !tmpDir.isDirectory())) { tmpDir.mkdirs(); } } if (needTimeSuffix) reportFile = new StringBuilder().append(reportDir) .append(report.getFile()).append("_") .append(statTime).append(".csv").toString(); else reportFile = new StringBuilder().append(reportDir) .append(report.getFile()).append(".csv").toString(); // 对周期性输出增加时间戳到文件结尾 if (report.isPeriod()) { if(report.isAppend()){ long beg=System.currentTimeMillis(); long currentRuntime = (beg + 8 * 60 * 60 * 1000) / 86400000; if(currentRuntime!=lastRuntime){ lastRuntime=currentRuntime; String bakFile=new StringBuilder().append(reportDir) .append(report.getFile()).append("_").append(statTime).append(".csv").toString(); new File(reportFile).renameTo(new File(bakFile)); } }else{ reportFile = new StringBuilder() .append(reportFile.substring(0, reportFile.indexOf(".csv"))).append("_") .append(System.currentTimeMillis()).append(".csv") .toString(); } } else { reportFile = reportFile + ".temp"; } createReportFileThreadPool.execute( new CreateReportOperation(reportFile,report,entryResultPool,reports,countDownLatch,outputEncoding)); reportFiles.add(reportFile); } try { boolean allexport = countDownLatch.await(10, TimeUnit.MINUTES); if (!allexport) { logger.error("3 minute use,but not export all reports!"); } } catch(Exception ex) { logger.error("generateReports error.",ex); } createTimeStampFile(normalDir.toString()); //清理lazy数据 ReportUtil.cleanLazyData(entryResultPool, statisticsRule.getEntryPool()); //清理period数据 ReportUtil.cleanPeriodData(entryResultPool, statisticsRule.getEntryPool()); if (logger.isWarnEnabled()) logger.warn(new StringBuilder("generate report ").append(id).append(" end") .append(", time consume: ") .append((System.currentTimeMillis() - start) / 1000) .toString()); return reports; } protected void createTimeStampFile(String dir) { // 创建一个报表输出时间戳文件,用于增量分析 String timeStampFile = new StringBuilder().append(dir) .append(AnalysisConstants.TIMESTAMP_FILE).toString(); BufferedWriter bwr = null; try { new File(timeStampFile).createNewFile(); bwr = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(new File(timeStampFile)))); bwr.write(String.valueOf(System.currentTimeMillis())); } catch (Exception ex) { logger.error("createTimeStampFile error!", ex); } finally { if (bwr != null) { try { bwr.close(); } catch (IOException e) { logger.error(e, e); } } } } @Override public void exportEntryData(Job job) { JobDataOperation jobDataOperation = new JobDataOperation(job,AnalysisConstants.JOBMANAGER_EVENT_EXPORTDATA,this.config); createReportFileThreadPool.execute(jobDataOperation); } @Override public void loadEntryData(Job job) { JobDataOperation jobDataOperation = new JobDataOperation(job,AnalysisConstants.JOBMANAGER_EVENT_LOADDATA,this.config); createReportFileThreadPool.submit(jobDataOperation); } @Override public void loadEntryDataToTmp(Job job) { JobDataOperation jobDataOperation = new JobDataOperation(job,AnalysisConstants.JOBMANAGER_EVENT_LOADDATA_TO_TMP,this.config); createReportFileThreadPool.submit(jobDataOperation); } @Override public void loadJobBackupData(Job job,String bckPrefix) { JobDataOperation jobDataOperation = new JobDataOperation(job,AnalysisConstants.JOBMANAGER_EVENT_LOAD_BACKUPDATA,this.config,bckPrefix); createReportFileThreadPool.submit(jobDataOperation); } }