/** * Copyright 2011 Intuit Inc. All Rights Reserved */ package com.intuit.tank.project; /* * #%L * JSF Support Beans * %% * Copyright (C) 2011 - 2015 Intuit Inc. * %% * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * #L% */ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.intuit.tank.util.Messages; import org.primefaces.event.NodeCollapseEvent; import org.primefaces.event.NodeExpandEvent; import org.primefaces.model.DefaultTreeNode; import org.primefaces.model.TreeNode; import org.primefaces.model.chart.ChartSeries; import com.intuit.tank.PreferencesBean; import com.intuit.tank.PropertyComparer; import com.intuit.tank.PropertyComparer.SortOrder; import com.intuit.tank.api.cloud.VMTracker; import com.intuit.tank.api.model.v1.cloud.CloudVmStatus; import com.intuit.tank.api.model.v1.cloud.CloudVmStatusContainer; import com.intuit.tank.api.model.v1.cloud.ProjectStatusContainer; import com.intuit.tank.api.model.v1.cloud.UserDetail; import com.intuit.tank.api.model.v1.cloud.ValidationStatus; import com.intuit.tank.auth.Security; import com.intuit.tank.dao.JobInstanceDao; import com.intuit.tank.dao.JobQueueDao; import com.intuit.tank.dao.ProjectDao; import com.intuit.tank.dao.WorkloadDao; import com.intuit.tank.job.ActJobNodeBean; import com.intuit.tank.job.JobNodeBean; import com.intuit.tank.job.ProjectNodeBean; import com.intuit.tank.job.VMNodeBean; import com.intuit.tank.prefs.TablePreferences; import com.intuit.tank.prefs.TableViewState; import com.intuit.tank.reporting.api.ResultsReader; import com.intuit.tank.reporting.api.TPSInfo; import com.intuit.tank.reporting.factory.ReportingFactory; import com.intuit.tank.util.ExceptionHandler; import com.intuit.tank.vm.common.util.MethodTimer; /** * JobTreeTableBean * * @author dangleton * */ public abstract class JobTreeTableBean implements Serializable { private static final String TOTAL_TPS_SERIES_KEY = "Total TPS"; private static final long serialVersionUID = 1L; private static final Logger LOG = LogManager.getLogger(JobTreeTableBean.class); private static final int MIN_REFRESH = 10; private static final int INITIAL_SIZE = 10; protected TreeNode rootNode; @Inject private VMTracker tracker; @Inject private Messages messages; @Inject private Security security; @Inject private PreferencesBean preferencesBean; @Inject private ExceptionHandler exceptionHandler; private ProjectDao projectDao = new ProjectDao(); private boolean filterFinished = true; private String refreshTimeSeconds; private int refreshInterval; private JobNodeBean currentJobInstance; private TrackingCartesianChartModel chartModel; private TrackingCartesianChartModel tpsChartModel; private List<String> allTpsKeys; private Map<String, List<String>> selectedTpsKeys = new HashMap<String, List<String>>(); protected abstract Integer getRootJobId(); private Map<String, TreeNode> nodeMap = new HashMap<String, TreeNode>(); // private Map<Date, Map<String, TPSInfo>> tpsMap = new HashMap<Date, Map<String, TPSInfo>>(); @Inject private PreferencesBean userPrefs; protected TablePreferences tablePrefs; protected TableViewState tableState = new TableViewState(); private Date lastDate = null; @PostConstruct public void init() { tablePrefs = new TablePreferences(userPrefs.getPreferences().getJobsTableColumns()); tablePrefs.registerListener(userPrefs); } /** * @return the tablePrefs */ public TablePreferences getTablePrefs() { return tablePrefs; } /** * @return the tableState */ public TableViewState getTableState() { return tableState; } public void deleteJobInstance(JobNodeBean bean) { if (bean.isDeleteable()) { try { JobInstance jobInstance = new JobInstanceDao().findById(Integer.parseInt(bean.getId())); JobQueueDao jobQueueDao = new JobQueueDao(); Workload workload = new WorkloadDao().findById(jobInstance.getWorkloadId()); JobQueue queue = jobQueueDao.findOrCreateForProjectId(workload.getProject().getId()); JobInstance instance = null; for (JobInstance job : queue.getJobs()) { if (job.getId() == jobInstance.getId()) { instance = job; break; } } if (instance != null) { queue.getJobs().remove(instance); queue = new JobQueueDao().saveOrUpdate(queue); } refreshData(); messages.info("Job " + jobInstance.getName() + " has been deleted."); } catch (Exception e) { LOG.error("Error deleting node with id of " + bean.getId(), e); exceptionHandler.handle(e); } } else { messages.warn(bean.getName() + " cannot be deleted."); } } /** * * @return */ public boolean isRefreshEnabled() { return refreshInterval >= MIN_REFRESH; } public JobNodeBean getCurrentJobInstance() { return currentJobInstance; } public void setCurrentJobInstance(JobNodeBean currentJobInstance) { this.currentJobInstance = currentJobInstance; } public void setCurrentJobInstanceForUser(JobNodeBean currentJobInstance) { setCurrentJobInstance(currentJobInstance); initChartModel(); } public void setCurrentJobInstanceForTPS(JobNodeBean currentJobInstance) { setCurrentJobInstance(currentJobInstance); // tpsMap.clear(); // lastDate = new Date(0); initializeTpsModel(); } public void keysChanged() { initializeTpsModel(); } /** * @return the allTpsKeys */ public List<String> getAllTpsKeys() { return allTpsKeys; } /** * @return the selectedTpsKeys */ public List<String> getSelectedTpsKeys() { if (currentJobInstance != null) { return selectedTpsKeys.get(currentJobInstance.getName()); } return null; } /** * @return the selectedTpsKeys */ public void setSelectedTpsKeys(List<String> keys) { if (currentJobInstance != null) { selectedTpsKeys.put(currentJobInstance.getName(), keys); } } /** * @return the chartModel */ public TrackingCartesianChartModel getChartModel() { return chartModel; } /** * @return the tpsChartModel */ public TrackingCartesianChartModel getTpsChartModel() { return tpsChartModel; } private void initChartModel() { LOG.info("Initializing user chart model..."); chartModel = null; if (currentJobInstance != null && currentJobInstance.getStatusDetailMap() != null) { chartModel = new TrackingCartesianChartModel(); Map<String, ChartSeries> seriesMap = new HashMap<String, ChartSeries>(); Map<Date, List<UserDetail>> detailMap = currentJobInstance.getStatusDetailMap(); List<Date> dateList = new ArrayList<Date>(detailMap.keySet()); Collections.sort(dateList); for (Date d : dateList) { for (UserDetail detail : detailMap.get(d)) { ChartSeries series = seriesMap.get(detail.getScript()); if (series == null) { series = new ChartSeries(detail.getScript()); chartModel.addSeries(series); seriesMap.put(detail.getScript(), series); } series.set(d.getTime(), detail.getUsers()); chartModel.addDate(d); } } chartModel.setExtender("userDetailsExtender"); } } private void initializeTpsModel() { LOG.info("Initializing TPS chart model..."); MethodTimer mt = new MethodTimer(LOG, getClass(), "initializeTpsModel"); tpsChartModel = null; if (currentJobInstance != null) { Set<String> keySet = new HashSet<String>(); List<String> list = selectedTpsKeys.get(currentJobInstance.getName()); boolean initKeys = false; if (list == null) { list = new ArrayList<String>(); list.add(TOTAL_TPS_SERIES_KEY); selectedTpsKeys.put(currentJobInstance.getName(), list); initKeys = true; } tpsChartModel = new TrackingCartesianChartModel(); tpsChartModel.setExtender("tpsDetailsExtender"); Map<String, ChartSeries> seriesMap = new HashMap<String, ChartSeries>(); Map<Date, Map<String, TPSInfo>> tpsDetailMap = getTpsMap(); mt.markAndLog("get tpsMap from DynamoDb"); List<Date> dateList = new ArrayList<Date>(tpsDetailMap.keySet()); Collections.sort(dateList); // if (dateList.size() > 0) { // lastDate = new Date(dateList.get(dateList.size() - 1).getTime() + 1000); // } ChartSeries totalSeries = new ChartSeries(TOTAL_TPS_SERIES_KEY); for (Date d : dateList) { int total = 0; for (TPSInfo info : tpsDetailMap.get(d).values()) { if (initKeys && list.size() < INITIAL_SIZE) { list.add(info.getKey()); } keySet.add(info.getKey()); if (list.contains(info.getKey())) { ChartSeries series = seriesMap.get(info.getKey()); if (series == null) { series = new ChartSeries(info.getKey()); tpsChartModel.addSeries(series); seriesMap.put(info.getKey(), series); } series.set(d.getTime(), info.getTPS()); } total += info.getTPS(); tpsChartModel.addDate(d); } totalSeries.set(d.getTime(), total); } if (list.contains(TOTAL_TPS_SERIES_KEY) && totalSeries.getData().size() > 0) { tpsChartModel.addSeries(totalSeries); } allTpsKeys = new ArrayList<String>(keySet); Collections.sort(allTpsKeys); allTpsKeys.add(0, TOTAL_TPS_SERIES_KEY); } else { LOG.info("currentJobInstance is null"); } mt.endAndLog(); } private Map<Date, Map<String, TPSInfo>> getTpsMap() { Map<Date, Map<String, TPSInfo>> ret = new HashMap<Date, Map<String, TPSInfo>>(); try { JobNodeBean vmInstance = null; List<JobNodeBean> jobNodes = new ArrayList<JobNodeBean>(); if (currentJobInstance.getType().equalsIgnoreCase("project")) { // get all jobIds if (currentJobInstance.getSubNodes() != null) { jobNodes.addAll(currentJobInstance.getSubNodes()); } } else if (currentJobInstance.getType().equalsIgnoreCase("job")) { jobNodes.add(currentJobInstance); } else if (currentJobInstance.getType().equalsIgnoreCase("vm")) { vmInstance = currentJobInstance; } ResultsReader resultsReader = ReportingFactory.getResultsReader(); if (!jobNodes.isEmpty()) { List<String> jobs = new ArrayList<String>(); for (JobNodeBean jobNode : jobNodes) { jobs.add(jobNode.getJobId()); } ret = resultsReader.getTpsMapForJob(this.lastDate, jobs.toArray(new String[jobs.size()])); } if (vmInstance != null) { ret = resultsReader.getTpsMapForInstance(this.lastDate, vmInstance.getJobId(), vmInstance.getId()); } } catch (Exception e) { LOG.error("Error getting TPS map."); } return ret; } /** * * @return */ public int getRefreshInterval() { return refreshInterval; } /** * @return the refreshTimeSeconds */ public String getRefreshTimeSeconds() { return refreshTimeSeconds; } /** * @param refreshTimeSeconds * the refreshTimeSeconds to set */ public void setRefreshTimeSeconds(String refreshTimeSeconds) { if (NumberUtils.isNumber(refreshTimeSeconds)) { int num = Integer.parseInt(refreshTimeSeconds); if (num >= MIN_REFRESH) { refreshInterval = num; this.refreshTimeSeconds = refreshTimeSeconds; } else { messages.warn("Refresh Interval must be at least " + MIN_REFRESH + " seconds."); } } else if (StringUtils.isEmpty(refreshTimeSeconds)) { refreshInterval = 0; this.refreshTimeSeconds = refreshTimeSeconds; } else { messages.warn("Refresh Interval must be an interger with a minimum value of " + MIN_REFRESH + " seconds."); } } /** * @return the filterFinished */ public boolean isFilterFinished() { return filterFinished; } /** * @param filterFinished * the filterFinished to set */ public void setFilterFinished(boolean filterFinished) { if (filterFinished != this.filterFinished) { this.filterFinished = filterFinished; refreshData(); } } /** * Refreshes the data nodes. */ public void refreshData() { rootNode = null; this.getRootNode(); } /** * @return the rootNode */ public TreeNode getRootNode() { if (rootNode == null) { buildTree(); updateExpansionStatus(rootNode); refreshCurrentJobInstance(rootNode); rootNode.setSelected(false); } return rootNode; } private void refreshCurrentJobInstance(TreeNode rootNode2) { JobNodeBean currJob = getCurrentJobInstance(); if (currJob != null && currJob.equals(rootNode2.getData())) { setCurrentJobInstance((JobNodeBean) rootNode2.getData()); return; } for (TreeNode node : rootNode2.getChildren()) { refreshCurrentJobInstance(node); } } private void updateExpansionStatus(TreeNode rootNode2) { for (TreeNode node : rootNode2.getChildren()) { updateExpansionStatus(node); if (node.getData() != null) { String id = ((JobNodeBean) node.getData()).getId(); if (nodeMap.get(id) != null) { node.setExpanded(nodeMap.get(id).isExpanded()); } nodeMap.put(id, node); } } } private void buildTree() { MethodTimer mt = new MethodTimer(LOG, this.getClass(), "buildTree"); Integer rootJob = getRootJobId(); JobQueueDao jqd = new JobQueueDao(); Set<String> trackerJobs = getTrackerJobIds(); mt.markAndLog("get tracker jobs"); Map<Integer, TreeNode> jobNodeMap = new HashMap<Integer, TreeNode>(); if (rootJob == null || rootJob == 0) { List<JobQueue> queuedJobs = jqd.findRecent(); mt.markAndLog("find all active jobs"); rootNode = new DefaultTreeNode("root", null); for (JobQueue jobQueue : queuedJobs) { TreeNode projectNode = createJobNode(trackerJobs, jobQueue); if (projectNode != null && projectNode.getChildCount() != 0) { jobNodeMap.put(jobQueue.getProjectId(), projectNode); projectNode.setParent(rootNode); rootNode.getChildren().add(projectNode); } } mt.markAndLog("Added all queued Jobs"); if (!trackerJobs.isEmpty()) { TreeNode unknownNode = new DefaultTreeNode(getProjectNodeBean("unknown"), null); for (String id : trackerJobs) {// left over nodes that the tracker is tracking // create job nodes now createAdhocJobNode(jqd, jobNodeMap, unknownNode, id); } if (unknownNode.getChildCount() > 0) { unknownNode.setParent(rootNode); rootNode.getChildren().add(unknownNode); } mt.markAndLog("Added all unknown Jobs"); } } else { JobQueue jobQueue = jqd.findOrCreateForProjectId(rootJob); rootNode = createJobNode(trackerJobs, jobQueue); } mt.endAndLog(); } // private void buildTree() { // MethodTimer mt = new MethodTimer(LOG, this.getClass(), "buildTree"); // Integer rootJob = getRootJobId(); // JobQueueDao jqd = new JobQueueDao(); // Set<String> trackerJobs = getTrackerJobIds(); // // mt.markAndLog("get tracker jobs"); // if (rootJob == null || rootJob == 0) { // JobInstanceDao jobInstanceDao = new JobInstanceDao(); // List<JobInstance> activeJobs = jobInstanceDao.findNotCompleted(); // mt.markAndLog("find all active jobs"); // // List<JobQueue> queuedJobs = jqd.findForJobs(activeJobs); // mt.markAndLog("find all active job queues"); // // List<JobQueue> queuedJobs = jqd.findAll(); // Collections.sort(queuedJobs, new PropertyComparer<JobQueue>(JobQueue.PROPERTY_PROJECT_ID)); // rootNode = new DefaultTreeNode("root", null); // for (JobQueue jobQueue : queuedJobs) { // TreeNode projectNode = createJobNode(trackerJobs, jobQueue); // if (projectNode != null && projectNode.getChildCount() != 0) { // projectNode.setParent(rootNode); // rootNode.getChildren().add(projectNode); // } // } // mt.markAndLog("Added all queued Jobs"); // if (!trackerJobs.isEmpty()) { // TreeNode projectNode = new DefaultTreeNode(getProjectNodeBean("unknown"), rootNode); // for (String id : trackerJobs) { // // create job nodes now // TreeNode adhocNode = createAdhocJobNode(id); // adhocNode.setParent(projectNode); // projectNode.getChildren().add(adhocNode); // } // mt.markAndLog("Added all unknown Jobs"); // } // } else { // JobQueue jobQueue = jqd.findOrCreateForProjectId(rootJob); // rootNode = createJobNode(trackerJobs, jobQueue); // } // mt.endAndLog(); // } /** * @param trackerJobs * @param jobQueue */ private TreeNode createJobNode(Set<String> trackerJobs, JobQueue jobQueue) { MethodTimer mt = new MethodTimer(LOG, getClass(), "createJobNode for project " + jobQueue.getProjectId()); TreeNode projectNode = null; Project p = projectDao.findById(jobQueue.getProjectId()); // mt.markAndLog("getProject"); if (p != null) { // Map<Date, Map<String, TPSInfo>> totalTPSDetails = new HashMap<Date, Map<String, TPSInfo>>(); ProjectNodeBean pnb = new ProjectNodeBean(p); boolean hasRights = security.isOwner(p); projectNode = new DefaultTreeNode(pnb, null); List<JobInstance> jobs = new ArrayList<JobInstance>(jobQueue.getJobs()); // mt.markAndLog("get jobs"); Collections.sort(jobs, new PropertyComparer<JobInstance>(JobInstance.PROPERTY_ID, SortOrder.DESCENDING)); int projectActive = 0; int projectTotal = 0; ValidationStatus projectFailures = new ValidationStatus(); for (JobInstance jobInstance : jobs) { CloudVmStatusContainer container = tracker.getVmStatusForJob(Integer.toString(jobInstance.getId())); trackerJobs.remove(Integer.toString(jobInstance.getId())); if (!filterFinished || jobInstance.getEndTime() == null) { ActJobNodeBean jobInstanceNode = new ActJobNodeBean(jobInstance, hasRights, preferencesBean.getDateTimeFormat()); pnb.addJob(jobInstanceNode); TreeNode jobNode = new DefaultTreeNode(jobInstanceNode, null); List<VMNodeBean> vmNodes = getVMStatus(jobInstance, hasRights); int jobInstanceActive = 0; int jobInstanceTotal = 0; ValidationStatus jobInstanceFailures = new ValidationStatus(); for (VMNodeBean vmNodeBean : vmNodes) { jobInstanceNode.addVMBean(vmNodeBean); new DefaultTreeNode(vmNodeBean, jobNode); if (NumberUtils.isNumber(vmNodeBean.getActiveUsers())) { jobInstanceActive += Integer.parseInt(vmNodeBean.getActiveUsers()); } if (NumberUtils.isNumber(vmNodeBean.getTotalUsers())) { jobInstanceTotal += Integer.parseInt(vmNodeBean.getTotalUsers()); } jobInstanceFailures.addFailures(vmNodeBean.getNumFailures()); jobInstanceNode.setTps(vmNodeBean.getTps() + jobInstanceNode.getTps()); } jobInstanceNode.setNumFailures(jobInstanceFailures); if (container != null) { jobInstanceNode.setUserDetails(container.getUserDetails()); jobInstanceNode.setStatusDetailMap(container.getDetailMap()); // jobInstanceNode.setTpsDetailMap(container.getTpsMap()); // combineTpsDetails(totalTPSDetails, container.getTpsMap()); } jobInstanceNode.setActiveUsers(Integer.toString(jobInstanceActive)); projectActive += jobInstanceActive; projectTotal += jobInstanceTotal; projectFailures.addFailures(jobInstanceFailures); jobNode.setParent(projectNode); projectNode.getChildren().add(jobNode); jobInstanceNode.reCalculate(); pnb.setTps(pnb.getTps() + jobInstanceNode.getTps()); } } pnb.setActiveUsers(Integer.toString(projectActive)); // mt.markAndLog("processed job instances."); pnb.setTotalUsers(Integer.toString(projectTotal)); // pnb.setTpsDetailMap(totalTPSDetails); pnb.setNumFailures(projectFailures); pnb.reCalculate(); ProjectStatusContainer projectStatusContainer = tracker.getProjectStatusContainer(Integer.toString(p .getId())); if (projectStatusContainer != null) { pnb.setUserDetails(projectStatusContainer.getUserDetails()); pnb.setStatusDetailMap(projectStatusContainer.getDetailMap()); } pnb.reCalculate(); } mt.endAndLog(); return projectNode; } private TreeNode createAdhocJobNode(JobQueueDao jqd, Map<Integer, TreeNode> jobNodeMap, TreeNode parent, String jobId) { // this needs to be a JobNode, not a projectNode // need to make new constructor for ActJobNodeBean that just sets empty strings? CloudVmStatusContainer container = tracker.getVmStatusForJob(jobId); ActJobNodeBean jobBeanNode = new ActJobNodeBean(jobId, container, preferencesBean.getDateTimeFormat()); JobQueue jq = jqd.findForJobId(Integer.valueOf(jobId)); if (jq != null) { TreeNode projectNode = jobNodeMap.get(jq.getProjectId()); if (projectNode != null) { parent = projectNode; } } TreeNode adhocNode = new DefaultTreeNode(jobBeanNode, null); List<VMNodeBean> vmNodes = getVMStatus(jobId, true); int nodeActive = 0; int nodeTotal = 0; ValidationStatus nodeFailures = new ValidationStatus(); for (VMNodeBean vmNodeBean : vmNodes) { new DefaultTreeNode(vmNodeBean, adhocNode); jobBeanNode.addVMBean(vmNodeBean); if (NumberUtils.isNumber(vmNodeBean.getActiveUsers())) { nodeActive += Integer.parseInt(vmNodeBean.getActiveUsers()); } if (NumberUtils.isNumber(vmNodeBean.getTotalUsers())) { nodeTotal += Integer.parseInt(vmNodeBean.getTotalUsers()); } nodeFailures.addFailures(vmNodeBean.getNumFailures()); jobBeanNode.setTps(jobBeanNode.getTps() + vmNodeBean.getTps()); } jobBeanNode.setStatusDetailMap(container.getDetailMap()); jobBeanNode.setJobId(jobId); jobBeanNode.setId(jobId); // jobBeanNode.setTpsDetailMap(container.getTpsMap()); jobBeanNode.setActiveUsers(Integer.toString(nodeActive)); jobBeanNode.setTotalUsers(Integer.toString(nodeTotal)); jobBeanNode.setNumFailures(nodeFailures); jobBeanNode.setUserDetails(container.getUserDetails()); jobBeanNode.reCalculate(); adhocNode.setParent(parent); parent.getChildren().add(adhocNode); return adhocNode; } /** * */ private Set<String> getTrackerJobIds() { Set<String> result = new HashSet<String>(); Set<CloudVmStatusContainer> allJobs = tracker.getAllJobs(); for (CloudVmStatusContainer c : allJobs) { result.add(c.getJobId()); } return result; } private List<VMNodeBean> getVMStatus(JobInstance jobInstance, boolean hasRights) { return getVMStatus(String.valueOf(jobInstance.getId()), hasRights); } private List<VMNodeBean> getVMStatus(String jobId, boolean hasRights) { List<VMNodeBean> vmNodes = new ArrayList<VMNodeBean>(); CloudVmStatusContainer container = tracker.getVmStatusForJob(jobId); if (container != null) { Set<CloudVmStatus> statuses = container.getStatuses(); for (CloudVmStatus cloudVmStatus : statuses) { VMNodeBean vmNode = new VMNodeBean(cloudVmStatus, hasRights, preferencesBean.getDateTimeFormat()); vmNode.setStatusDetailMap(container.getDetailMap()); vmNode.setTps(cloudVmStatus.getTotalTps()); vmNodes.add(vmNode); } } return vmNodes; } private ProjectNodeBean getProjectNodeBean(String projectName) { return new ProjectNodeBean(projectName); } public void onNodeExpand(NodeExpandEvent event) { JobNodeBean jnb = (JobNodeBean) event.getTreeNode().getData(); String id = jnb.getId(); nodeMap.get(id).setExpanded(true); } public void onNodeCollapse(NodeCollapseEvent event) { JobNodeBean jnb = (JobNodeBean) event.getTreeNode().getData(); String id = jnb.getId(); TreeNode node = nodeMap.get(id); node.setExpanded(false); TreeNode parent = node.getParent(); while (parent != null) { parent.setExpanded(true); parent = parent.getParent(); } } public boolean canControlJob(JobNodeBean node) { return node.canControlJob(security); } }