/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.hadoop.mapred.gridmix.test.system; import java.io.IOException; import java.io.File; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.SortedMap; import java.util.TreeMap; import java.util.Collections; import java.util.Set; import java.util.ArrayList; import java.util.Arrays; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.mapred.Counters; import org.apache.hadoop.mapred.Counters.Counter; import org.apache.hadoop.mapred.Counters.Group; import org.apache.hadoop.mapred.Task; import org.apache.hadoop.mapred.DefaultJobHistoryParser; import org.apache.hadoop.mapred.JobHistory; import org.apache.hadoop.mapreduce.JobID; import org.apache.hadoop.mapreduce.TaskType; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.test.system.JTClient; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.tools.rumen.LoggedJob; import org.apache.hadoop.tools.rumen.ZombieJob; import org.apache.hadoop.tools.rumen.TaskInfo; import org.junit.Assert; import java.text.ParseException; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.mapred.gridmix.GridmixSystemTestCase; /** * Verifying each Gridmix job with corresponding job story in a trace file. */ public class GridmixJobVerification { private static Log LOG = LogFactory.getLog(GridmixJobVerification.class); private Path path; private Configuration conf; private JTClient jtClient; private String userResolverVal; static final String origJobIdKey = GridMixConfig.GRIDMIX_ORIGINAL_JOB_ID; static final String jobSubKey = GridMixConfig.GRIDMIX_SUBMISSION_POLICY; static final String jobTypeKey = GridMixConfig.GRIDMIX_JOB_TYPE; static final String mapTaskKey = GridMixConfig.GRIDMIX_SLEEPJOB_MAPTASK_ONLY; static final String usrResolver = GridMixConfig.GRIDMIX_USER_RESOLVER; static final String fileOutputFormatKey = "mapred.output.compress"; static final String fileInputFormatKey = "mapred.input.dir"; static final String compEmulKey = GridMixConfig.GRIDMIX_COMPRESSION_ENABLE; static final String inputDecompKey = GridMixConfig.GRIDMIX_INPUT_DECOMPRESS_ENABLE; static final String mapInputCompRatio = GridMixConfig.GRIDMIX_INPUT_COMPRESS_RATIO; static final String mapOutputCompRatio = GridMixConfig.GRIDMIX_INTERMEDIATE_COMPRESSION_RATIO; static final String reduceOutputCompRatio = GridMixConfig.GRIDMIX_OUTPUT_COMPRESSION_RATIO; private Map<String, List<JobConf>> simuAndOrigJobsInfo = new HashMap<String, List<JobConf>>(); /** * Gridmix job verification constructor * @param path - path of the gridmix output directory. * @param conf - cluster configuration. * @param jtClient - jobtracker client. */ public GridmixJobVerification(Path path, Configuration conf, JTClient jtClient) { this.path = path; this.conf = conf; this.jtClient = jtClient; } /** * It verifies the Gridmix jobs with corresponding job story in a trace file. * @param jobids - gridmix job ids. * @throws IOException - if an I/O error occurs. * @throws ParseException - if an parse error occurs. */ public void verifyGridmixJobsWithJobStories(List<JobID> jobids) throws Exception { SortedMap <Long, String> origSubmissionTime = new TreeMap <Long, String>(); SortedMap <Long, String> simuSubmissionTime = new TreeMap<Long, String>(); GridmixJobStory gjs = new GridmixJobStory(path, conf); final Iterator<JobID> ite = jobids.iterator(); File destFolder = new File(System.getProperty("java.io.tmpdir") + "/gridmix-st/"); destFolder.mkdir(); while (ite.hasNext()) { JobID simuJobId = ite.next(); JobHistory.JobInfo jhInfo = getSimulatedJobHistory(simuJobId); Assert.assertNotNull("Job history not found.", jhInfo); Counters counters = Counters.fromEscapedCompactString(jhInfo.getValues() .get(JobHistory.Keys.COUNTERS)); JobConf simuJobConf = getSimulatedJobConf(simuJobId, destFolder); int cnt = 1; do { if (simuJobConf != null) { break; } Thread.sleep(100); simuJobConf = getSimulatedJobConf(simuJobId, destFolder); cnt++; } while(cnt < 30); String origJobId = simuJobConf.get(origJobIdKey); LOG.info("OriginalJobID<->CurrentJobID:" + origJobId + "<->" + simuJobId); if (userResolverVal == null) { userResolverVal = simuJobConf.get(usrResolver); } ZombieJob zombieJob = gjs.getZombieJob(JobID.forName(origJobId)); Map<String, Long> mapJobCounters = getJobMapCounters(zombieJob); Map<String, Long> reduceJobCounters = getJobReduceCounters(zombieJob); if (simuJobConf.get(jobSubKey).contains("REPLAY")) { origSubmissionTime.put(zombieJob.getSubmissionTime(), origJobId.toString() + "^" + simuJobId); simuSubmissionTime.put(Long.parseLong(jhInfo.getValues().get(JobHistory.Keys.SUBMIT_TIME)), origJobId.toString() + "^" + simuJobId); ; } LOG.info("Verifying the job <" + simuJobId + "> and wait for a while..."); verifySimulatedJobSummary(zombieJob, jhInfo, simuJobConf); verifyJobMapCounters(counters, mapJobCounters, simuJobConf); verifyJobReduceCounters(counters, reduceJobCounters, simuJobConf); verifyCompressionEmulation(zombieJob.getJobConf(), simuJobConf, counters, reduceJobCounters, mapJobCounters); verifyDistributeCache(zombieJob,simuJobConf); setJobDistributedCacheInfo(simuJobId.toString(), simuJobConf, zombieJob.getJobConf()); verifyHighRamMemoryJobs(zombieJob, simuJobConf); verifyCPUEmulationOfJobs(zombieJob, jhInfo, simuJobConf); verifyMemoryEmulationOfJobs(zombieJob, jhInfo, simuJobConf); LOG.info("Done."); } verifyDistributedCacheBetweenJobs(simuAndOrigJobsInfo); } /** * Verify the job submission order between the jobs in replay mode. * @param origSubmissionTime - sorted map of original jobs submission times. * @param simuSubmissionTime - sorted map of simulated jobs submission times. */ public void verifyJobSumissionTime(SortedMap<Long, String> origSubmissionTime, SortedMap<Long, String> simuSubmissionTime) { Assert.assertEquals("Simulated job's submission time count has " + "not match with Original job's submission time count.", origSubmissionTime.size(), simuSubmissionTime.size()); for ( int index = 0; index < origSubmissionTime.size(); index ++) { String origAndSimuJobID = origSubmissionTime.get(index); String simuAndorigJobID = simuSubmissionTime.get(index); Assert.assertEquals("Simulated jobs have not submitted in same " + "order as original jobs submitted in REPLAY mode.", origAndSimuJobID, simuAndorigJobID); } } /** * It verifies the simulated job map counters. * @param counters - Original job map counters. * @param mapJobCounters - Simulated job map counters. * @param jobConf - Simulated job configuration. * @throws ParseException - If an parser error occurs. */ public void verifyJobMapCounters(Counters counters, Map<String,Long> mapCounters, JobConf jobConf) throws ParseException { if (!jobConf.get(jobTypeKey, "LOADJOB").equals("SLEEPJOB")) { Assert.assertEquals("Map input records have not matched.", mapCounters.get("MAP_INPUT_RECS").longValue(), getCounterValue(counters, "MAP_INPUT_RECORDS")); } else { Assert.assertTrue("Map Input Bytes are zero", getCounterValue(counters,"HDFS_BYTES_READ") != 0); Assert.assertNotNull("Map Input Records are zero", getCounterValue(counters, "MAP_INPUT_RECORDS")!=0); } } /** * It verifies the simulated job reduce counters. * @param counters - Original job reduce counters. * @param reduceCounters - Simulated job reduce counters. * @param jobConf - simulated job configuration. * @throws ParseException - if an parser error occurs. */ public void verifyJobReduceCounters(Counters counters, Map<String,Long> reduceCounters, JobConf jobConf) throws ParseException { if (jobConf.get(jobTypeKey, "LOADJOB").equals("SLEEPJOB")) { Assert.assertTrue("Reduce output records are not zero for sleep job.", getCounterValue(counters, "REDUCE_OUTPUT_RECORDS") == 0); Assert.assertTrue("Reduce output bytes are not zero for sleep job.", getCounterValue(counters,"HDFS_BYTES_WRITTEN") == 0); } } /** * It verifies the gridmix simulated job summary. * @param zombieJob - Original job summary. * @param jhInfo - Simulated job history info. * @param jobConf - simulated job configuration. * @throws IOException - if an I/O error occurs. */ public void verifySimulatedJobSummary(ZombieJob zombieJob, JobHistory.JobInfo jhInfo, JobConf jobConf) throws IOException { Assert.assertEquals("Job id has not matched", zombieJob.getJobID(), JobID.forName(jobConf.get(origJobIdKey))); Assert.assertEquals("Job maps have not matched", String.valueOf(zombieJob.getNumberMaps()), jhInfo.getValues().get(JobHistory.Keys.TOTAL_MAPS)); if (!jobConf.getBoolean(mapTaskKey, false)) { Assert.assertEquals("Job reducers have not matched", String.valueOf(zombieJob.getNumberReduces()), jhInfo.getValues().get(JobHistory.Keys.TOTAL_REDUCES)); } else { Assert.assertEquals("Job reducers have not matched", 0, Integer.parseInt(jhInfo.getValues().get(JobHistory.Keys.TOTAL_REDUCES))); } Assert.assertEquals("Job status has not matched.", zombieJob.getOutcome().name(), convertJobStatus(jhInfo.getValues().get(JobHistory.Keys.JOB_STATUS))); LoggedJob loggedJob = zombieJob.getLoggedJob(); Assert.assertEquals("Job priority has not matched.", loggedJob.getPriority().toString(), jhInfo.getValues().get(JobHistory.Keys.JOB_PRIORITY)); if (jobConf.get(usrResolver).contains("RoundRobin")) { String user = UserGroupInformation.getLoginUser().getShortUserName(); Assert.assertTrue(jhInfo.getValues().get(JobHistory.Keys.JOBID).toString() + " has not impersonate with other user.", !jhInfo.getValues().get(JobHistory.Keys.USER).equals(user)); } } /** * Get the original job map counters from a trace. * @param zombieJob - Original job story. * @return - map counters as a map. */ public Map<String, Long> getJobMapCounters(ZombieJob zombieJob) { long expMapInputBytes = 0; long expMapOutputBytes = 0; long expMapInputRecs = 0; long expMapOutputRecs = 0; Map<String,Long> mapCounters = new HashMap<String,Long>(); for (int index = 0; index < zombieJob.getNumberMaps(); index ++) { TaskInfo mapTask = zombieJob.getTaskInfo(TaskType.MAP, index); expMapInputBytes += mapTask.getInputBytes(); expMapOutputBytes += mapTask.getOutputBytes(); expMapInputRecs += mapTask.getInputRecords(); expMapOutputRecs += mapTask.getOutputRecords(); } mapCounters.put("MAP_INPUT_BYTES", expMapInputBytes); mapCounters.put("MAP_OUTPUT_BYTES", expMapOutputBytes); mapCounters.put("MAP_INPUT_RECS", expMapInputRecs); mapCounters.put("MAP_OUTPUT_RECS", expMapOutputRecs); return mapCounters; } /** * Get the original job reduce counters from a trace. * @param zombieJob - Original job story. * @return - reduce counters as a map. */ public Map<String,Long> getJobReduceCounters(ZombieJob zombieJob) { long expReduceInputBytes = 0; long expReduceOutputBytes = 0; long expReduceInputRecs = 0; long expReduceOutputRecs = 0; Map<String,Long> reduceCounters = new HashMap<String,Long>(); for (int index = 0; index < zombieJob.getNumberReduces(); index ++) { TaskInfo reduceTask = zombieJob.getTaskInfo(TaskType.REDUCE, index); expReduceInputBytes += reduceTask.getInputBytes(); expReduceOutputBytes += reduceTask.getOutputBytes(); expReduceInputRecs += reduceTask.getInputRecords(); expReduceOutputRecs += reduceTask.getOutputRecords(); } reduceCounters.put("REDUCE_INPUT_BYTES", expReduceInputBytes); reduceCounters.put("REDUCE_OUTPUT_BYTES", expReduceOutputBytes); reduceCounters.put("REDUCE_INPUT_RECS", expReduceInputRecs); reduceCounters.put("REDUCE_OUTPUT_RECS", expReduceOutputRecs); return reduceCounters; } /** * Get the simulated job configuration of a job. * @param simulatedJobID - Simulated job id. * @param tmpJHFolder - temporary job history folder location. * @return - simulated job configuration. * @throws IOException - If an I/O error occurs. */ public JobConf getSimulatedJobConf(JobID simulatedJobID, File tmpJHFolder) throws IOException, InterruptedException { FileSystem fs = null; try { String historyFilePath = jtClient.getProxy() .getJobHistoryLocationForRetiredJob(simulatedJobID); int cnt = 0; do { if (historyFilePath != null) { break; } Thread.sleep(100); historyFilePath = jtClient.getProxy() .getJobHistoryLocationForRetiredJob(simulatedJobID); cnt++; } while( cnt < 30 ); Assert.assertNotNull("History file has not available for the job [" + simulatedJobID + "] for 3 secs.", historyFilePath); Path jhpath = new Path(historyFilePath); LOG.info("Parent:" + jhpath.getParent()); fs = jhpath.getFileSystem(conf); fs.copyToLocalFile(jhpath,new Path(tmpJHFolder.toString())); fs.copyToLocalFile(new Path(jhpath.getParent() + "/" + simulatedJobID + "_conf.xml"), new Path(tmpJHFolder.toString())); JobConf jobConf = new JobConf(); jobConf.addResource(new Path(tmpJHFolder.toString() + "/" + simulatedJobID + "_conf.xml")); jobConf.reloadConfiguration(); return jobConf; }finally { fs.close(); } } /** * Get the simulated job history of a job. * @param simulatedJobID - simulated job id. * @return - simulated job information. * @throws IOException - if an I/O error occurs. */ public JobHistory.JobInfo getSimulatedJobHistory(JobID simulatedJobID) throws IOException, InterruptedException { FileSystem fs = null; try { String historyFilePath = jtClient.getProxy(). getJobHistoryLocationForRetiredJob(simulatedJobID); int cnt = 0; do { if (historyFilePath != null) { break; } Thread.sleep(100); historyFilePath = jtClient.getProxy() .getJobHistoryLocationForRetiredJob(simulatedJobID); cnt++; } while( cnt < 30 ); LOG.info("HistoryFilePath:" + historyFilePath); Assert.assertNotNull("History file path has not found for a job[" + simulatedJobID + "] for 3 secs."); Path jhpath = new Path(historyFilePath); fs = jhpath.getFileSystem(conf); JobHistory.JobInfo jobInfo = new JobHistory.JobInfo(simulatedJobID.toString()); DefaultJobHistoryParser.parseJobTasks(historyFilePath, jobInfo, fs); return jobInfo; } finally { fs.close(); } } /** * It verifies the cpu resource usage of gridmix jobs against * the original job cpu resource usage. * @param origJobHistory - Original job history. * @param simuJobHistoryInfo - Simulated job history. * @param simuJobConf - simulated job configuration. */ public void verifyCPUEmulationOfJobs(ZombieJob origJobHistory, JobHistory.JobInfo simuJobHistoryInfo, JobConf simuJobConf) throws Exception { boolean isCPUEmulON = false; if (simuJobConf.get(GridMixConfig.GRIDMIX_CPU_EMULATION) != null) { isCPUEmulON = simuJobConf.get(GridMixConfig.GRIDMIX_CPU_EMULATION). contains(GridMixConfig.GRIDMIX_CPU_EMULATION_PLUGIN); } if (isCPUEmulON) { Map<String,Long> origJobMetrics = getOriginalJobCPUMetrics(origJobHistory); Map<String,Long> simuJobMetrics = getSimulatedJobCPUMetrics(simuJobHistoryInfo); long origMapUsage = origJobMetrics.get("MAP"); LOG.info("Total cpu usage of Maps for a original job:" + origMapUsage); long origReduceUsage = origJobMetrics.get("REDUCE"); LOG.info("Total cpu usage of Reduces for a original job:" + origReduceUsage); long simuMapUsage = simuJobMetrics.get("MAP"); LOG.info("Total cpu usage of Maps for a simulated job:" + simuMapUsage); long simuReduceUsage = simuJobMetrics.get("REDUCE"); LOG.info("Total cpu usage of Reduces for a simulated job:" + simuReduceUsage); int mapCount = Integer.parseInt( simuJobHistoryInfo.getValues().get(JobHistory.Keys.TOTAL_MAPS)); int reduceCount = Integer.parseInt( simuJobHistoryInfo.getValues().get(JobHistory.Keys.TOTAL_REDUCES)); if (mapCount > 0) { double mapEmulFactor = (simuMapUsage * 100) / origMapUsage; long mapEmulAccuracy = Math.round(mapEmulFactor); LOG.info("CPU emulation accuracy for maps in job " + simuJobHistoryInfo.getValues().get(JobHistory.Keys.JOBID) + ":"+ mapEmulAccuracy + "%"); Assert.assertTrue("Map-side cpu emulaiton inaccurate!" + " Actual cpu usage: " + simuMapUsage + " Expected cpu usage: " + origMapUsage, mapEmulAccuracy >= GridMixConfig.GRIDMIX_CPU_EMULATION_LOWER_LIMIT && mapEmulAccuracy <= GridMixConfig.GRIDMIX_CPU_EMULATION_UPPER_LIMIT); } if (reduceCount >0) { double reduceEmulFactor = (simuReduceUsage * 100) / origReduceUsage; long reduceCPUUsage = simuReduceUsage / 1000; LOG.info("Reduce CPU Usage:" + reduceCPUUsage); LOG.info("Reduce emulation factor:" + reduceEmulFactor); long reduceEmulAccuracy = Math.round(reduceEmulFactor); LOG.info("CPU emulation accuracy for reduces in job " + simuJobHistoryInfo.getValues().get(JobHistory.Keys.JOBID) + ": " + reduceEmulAccuracy + "%"); if ( reduceCPUUsage >= 10 ) { Assert.assertTrue("Reduce side cpu emulaiton inaccurate!" + " Actual cpu usage:" + simuReduceUsage + "Expected cpu usage: " + origReduceUsage, reduceEmulAccuracy >= GridMixConfig.GRIDMIX_CPU_EMULATION_LOWER_LIMIT && reduceEmulAccuracy <= GridMixConfig.GRIDMIX_CPU_EMULATION_UPPER_LIMIT); } else { Assert.assertTrue("Reduce side cpu emulaiton inaccurate!" + " Actual cpu usage:" + simuReduceUsage + "Expected cpu usage: " + origReduceUsage, reduceEmulAccuracy >= 60 && reduceEmulAccuracy <= 100); } } } } /** * It verifies the heap memory resource usage of gridmix jobs with * corresponding original job in the trace. * @param zombieJob - Original job history. * @param jhInfo - Simulated job history. * @param simuJobConf - simulated job configuration. */ public void verifyMemoryEmulationOfJobs(ZombieJob zombieJob, JobHistory.JobInfo jhInfo, JobConf simuJobConf) throws Exception { long origJobMapsTHU = 0; long origJobReducesTHU = 0; long simuJobMapsTHU = 0; long simuJobReducesTHU = 0; boolean isMemEmulOn = false; String strHeapRatio = "0.3F"; if (simuJobConf.get(GridMixConfig.GRIDMIX_MEMORY_EMULATION) != null) { isMemEmulOn = simuJobConf.get(GridMixConfig.GRIDMIX_MEMORY_EMULATION). contains(GridMixConfig.GRIDMIX_MEMORY_EMULATION_PLUGIN); } if (isMemEmulOn) { for (int index = 0; index < zombieJob.getNumberMaps(); index ++) { TaskInfo mapTask = zombieJob.getTaskInfo(TaskType.MAP, index); if (mapTask.getResourceUsageMetrics().getHeapUsage() > 0) { origJobMapsTHU += mapTask.getResourceUsageMetrics().getHeapUsage(); } } LOG.info("Total Heap Usage of Maps for original job: " + origJobMapsTHU); for (int index = 0; index < zombieJob.getNumberReduces(); index ++) { TaskInfo reduceTask = zombieJob.getTaskInfo(TaskType.REDUCE, index); if (reduceTask.getResourceUsageMetrics().getHeapUsage() > 0) { origJobReducesTHU += reduceTask.getResourceUsageMetrics().getHeapUsage(); } } LOG.info("Total Heap Usage of Reduces for original job: " + origJobReducesTHU); Counters mapCounters = Counters.fromEscapedCompactString(jhInfo.getValues() .get(JobHistory.Keys.MAP_COUNTERS)); Counters reduceCounters = Counters.fromEscapedCompactString(jhInfo.getValues() .get(JobHistory.Keys.REDUCE_COUNTERS)); simuJobMapsTHU = getCounterValue(mapCounters, Task.Counter.COMMITTED_HEAP_BYTES.toString()); LOG.info("Simulated Job Maps Total Heap Usage: " + simuJobMapsTHU); simuJobReducesTHU = getCounterValue(reduceCounters, Task.Counter.COMMITTED_HEAP_BYTES.toString()); LOG.info("Simulated Jobs Reduces Total Heap Usage: " + simuJobReducesTHU); long mapCount = Integer.parseInt(jhInfo.getValues() .get(JobHistory.Keys.TOTAL_MAPS)); long reduceCount = Integer.parseInt(jhInfo.getValues() .get(JobHistory.Keys.TOTAL_REDUCES)); if (simuJobConf.get(GridMixConfig .GRIDMIX_HEAP_FREE_MEMORY_RATIO) != null) { strHeapRatio = "0.3F"; } if (mapCount > 0) { double mapEmulFactor = (simuJobMapsTHU * 100) / origJobMapsTHU; long mapEmulAccuracy = Math.round(mapEmulFactor); LOG.info("Maps memory emulation accuracy of a job:" + mapEmulAccuracy + "%"); Assert.assertTrue("Map phase total memory emulation had crossed the " + "configured max limit.", mapEmulAccuracy <= GridMixConfig.GRIDMIX_MEMORY_EMULATION_UPPER_LIMIT); Assert.assertTrue("Map phase total memory emulation had not crossed " + "the configured min limit.", mapEmulAccuracy >= GridMixConfig.GRIDMIX_MEMORY_EMULATION_LOWER_LIMIT); double expHeapRatio = Double.parseDouble(strHeapRatio); LOG.info("expHeapRatio for maps:" + expHeapRatio); double actHeapRatio = ((double)Math.abs(origJobMapsTHU - simuJobMapsTHU)) ; actHeapRatio /= origJobMapsTHU; LOG.info("actHeapRatio for maps:" + actHeapRatio); Assert.assertTrue("Simulate job maps heap ratio not matched.", actHeapRatio <= expHeapRatio); } if (reduceCount >0) { double reduceEmulFactor = (simuJobReducesTHU * 100) / origJobReducesTHU; long reduceEmulAccuracy = Math.round(reduceEmulFactor); LOG.info("Reduces memory emulation accuracy of a job:" + reduceEmulAccuracy + "%"); Assert.assertTrue("Reduce phase total memory emulation had crossed " + "configured max limit.", reduceEmulAccuracy <= GridMixConfig.GRIDMIX_MEMORY_EMULATION_UPPER_LIMIT); Assert.assertTrue("Reduce phase total memory emulation had not " + "crosssed configured min limit.", reduceEmulAccuracy >= GridMixConfig.GRIDMIX_MEMORY_EMULATION_LOWER_LIMIT); double expHeapRatio = Double.parseDouble(strHeapRatio); LOG.info("expHeapRatio for reduces:" + expHeapRatio); double actHeapRatio = ((double)Math.abs(origJobReducesTHU - simuJobReducesTHU)); actHeapRatio /= origJobReducesTHU; LOG.info("actHeapRatio for reduces:" + actHeapRatio); Assert.assertTrue("Simulate job reduces heap ratio not matched.", actHeapRatio <= expHeapRatio); } } } /** * Get the simulated job cpu metrics. * @param jhInfo - Simulated job history * @return - cpu metrics as a map. * @throws Exception - if an error occurs. */ private Map<String,Long> getSimulatedJobCPUMetrics( JobHistory.JobInfo jhInfo) throws Exception { Map<String, Long> resourceMetrics = new HashMap<String, Long>(); Counters mapCounters = Counters.fromEscapedCompactString( jhInfo.getValues().get(JobHistory.Keys.MAP_COUNTERS)); long mapCPUUsage = getCounterValue(mapCounters, Task.Counter.CPU_MILLISECONDS.toString()); resourceMetrics.put("MAP", mapCPUUsage); Counters reduceCounters = Counters.fromEscapedCompactString( jhInfo.getValues().get(JobHistory.Keys.REDUCE_COUNTERS)); long reduceCPUUsage = getCounterValue(reduceCounters, Task.Counter.CPU_MILLISECONDS.toString()); resourceMetrics.put("REDUCE", reduceCPUUsage); return resourceMetrics; } /** * Get the original job cpu metrics. * @param zombieJob - original job history. * @return - cpu metrics as map. */ private Map<String, Long> getOriginalJobCPUMetrics(ZombieJob zombieJob) { long mapTotalCPUUsage = 0; long reduceTotalCPUUsage = 0; Map<String,Long> resourceMetrics = new HashMap<String,Long>(); for (int index = 0; index < zombieJob.getNumberMaps(); index++) { TaskInfo mapTask = zombieJob.getTaskInfo(TaskType.MAP, index); if (mapTask.getResourceUsageMetrics().getCumulativeCpuUsage() > 0) { mapTotalCPUUsage += mapTask.getResourceUsageMetrics().getCumulativeCpuUsage(); } } resourceMetrics.put("MAP", mapTotalCPUUsage); for (int index = 0; index < zombieJob.getNumberReduces(); index++) { TaskInfo reduceTask = zombieJob.getTaskInfo(TaskType.REDUCE, index); if (reduceTask.getResourceUsageMetrics().getCumulativeCpuUsage() > 0) { reduceTotalCPUUsage += reduceTask.getResourceUsageMetrics().getCumulativeCpuUsage(); } } resourceMetrics.put("REDUCE", reduceTotalCPUUsage); return resourceMetrics; } /** * Get the user resolver of a job. */ public String getJobUserResolver() { return userResolverVal; } /** * It verifies the compression ratios of mapreduce jobs. * @param origJobConf - original job configuration. * @param simuJobConf - simulated job configuration. * @param counters - simulated job counters. * @param origReduceCounters - original job reduce counters. * @param origMapCounters - original job map counters. * @throws ParseException - if a parser error occurs. * @throws IOException - if an I/O error occurs. */ public void verifyCompressionEmulation(JobConf origJobConf, JobConf simuJobConf,Counters counters, Map<String, Long> origReduceCounters, Map<String, Long> origMapJobCounters) throws ParseException,IOException { if (simuJobConf.getBoolean(compEmulKey, false)) { String inputDir = origJobConf.get(fileInputFormatKey); Assert.assertNotNull(fileInputFormatKey + " is Null",inputDir); long simMapInputBytes = getCounterValue(counters, "HDFS_BYTES_READ"); long uncompressedInputSize = origMapJobCounters.get("MAP_INPUT_BYTES"); long simReduceInputBytes = getCounterValue(counters, "REDUCE_SHUFFLE_BYTES"); long simMapOutputBytes = getCounterValue(counters, "MAP_OUTPUT_BYTES"); // Verify input compression whether it's enable or not. if (inputDir.contains(".gz") || inputDir.contains(".tgz") || inputDir.contains(".bz")) { Assert.assertTrue("Input decompression attribute has been not set for " + "for compressed input", simuJobConf.getBoolean(inputDecompKey, false)); float INPUT_COMP_RATIO = getExpectedCompressionRatio(simuJobConf, mapInputCompRatio); float INTERMEDIATE_COMP_RATIO = getExpectedCompressionRatio(simuJobConf, mapOutputCompRatio); // Verify Map Input Compression Ratio. assertMapInputCompressionRatio(simMapInputBytes, uncompressedInputSize, INPUT_COMP_RATIO); // Verify Map Output Compression Ratio. assertMapOuputCompressionRatio(simReduceInputBytes, simMapOutputBytes, INTERMEDIATE_COMP_RATIO); } else { Assert.assertEquals("MAP input bytes has not matched.", convertBytes(uncompressedInputSize), convertBytes(simMapInputBytes)); } Assert.assertEquals("Simulated job output format has not matched with " + "original job output format.", origJobConf.getBoolean(fileOutputFormatKey,false), simuJobConf.getBoolean(fileOutputFormatKey,false)); if (simuJobConf.getBoolean(fileOutputFormatKey,false)) { float OUTPUT_COMP_RATIO = getExpectedCompressionRatio(simuJobConf, reduceOutputCompRatio); //Verify reduce output compression ratio. long simReduceOutputBytes = getCounterValue(counters, "HDFS_BYTES_WRITTEN"); long origReduceOutputBytes = origReduceCounters.get("REDUCE_OUTPUT_BYTES"); assertReduceOutputCompressionRatio(simReduceOutputBytes, origReduceOutputBytes, OUTPUT_COMP_RATIO); } } } private void assertMapInputCompressionRatio(long simMapInputBytes, long origMapInputBytes, float expInputCompRatio) { LOG.info("***Verify the map input bytes compression ratio****"); LOG.info("Simulated job's map input bytes(REDUCE_SHUFFLE_BYTES): " + simMapInputBytes); LOG.info("Original job's map input bytes: " + origMapInputBytes); final float actInputCompRatio = getActualCompressionRatio(simMapInputBytes, origMapInputBytes); LOG.info("Expected Map Input Compression Ratio:" + expInputCompRatio); LOG.info("Actual Map Input Compression Ratio:" + actInputCompRatio); float diffVal = (float)(expInputCompRatio * 0.06); LOG.info("Expected Difference of Map Input Compression Ratio is <= " + + diffVal); float delta = Math.abs(expInputCompRatio - actInputCompRatio); LOG.info("Actual Difference of Map Iput Compression Ratio:" + delta); Assert.assertTrue("Simulated job input compression ratio has mismatched.", delta <= diffVal); LOG.info("******Done******"); } private void assertMapOuputCompressionRatio(long simReduceInputBytes, long simMapoutputBytes, float expMapOuputCompRatio) { LOG.info("***Verify the map output bytes compression ratio***"); LOG.info("Simulated job reduce input bytes:" + simReduceInputBytes); LOG.info("Simulated job map output bytes:" + simMapoutputBytes); final float actMapOutputCompRatio = getActualCompressionRatio(simReduceInputBytes, simMapoutputBytes); LOG.info("Expected Map Output Compression Ratio:" + expMapOuputCompRatio); LOG.info("Actual Map Output Compression Ratio:" + actMapOutputCompRatio); float diffVal = 0.05f; LOG.info("Expected Difference Of Map Output Compression Ratio is <= " + diffVal); float delta = Math.abs(expMapOuputCompRatio - actMapOutputCompRatio); LOG.info("Actual Difference Of Map Ouput Compression Ratio :" + delta); Assert.assertTrue("Simulated job map output compression ratio " + "has not been matched.", delta <= diffVal); LOG.info("******Done******"); } private void assertReduceOutputCompressionRatio(long simReduceOutputBytes, long origReduceOutputBytes , float expOutputCompRatio ) { LOG.info("***Verify the reduce output bytes compression ratio***"); final float actOuputputCompRatio = getActualCompressionRatio(simReduceOutputBytes, origReduceOutputBytes); LOG.info("Simulated job's reduce output bytes:" + simReduceOutputBytes); LOG.info("Original job's reduce output bytes:" + origReduceOutputBytes); LOG.info("Expected output compression ratio:" + expOutputCompRatio); LOG.info("Actual output compression ratio:" + actOuputputCompRatio); long diffVal = (long)(origReduceOutputBytes * 0.15); long delta = Math.abs(origReduceOutputBytes - simReduceOutputBytes); LOG.info("Expected difference of output compressed bytes is <= " + diffVal); LOG.info("Actual difference of compressed ouput bytes:" + delta); Assert.assertTrue("Simulated job reduce output compression ratio " + "has not been matched.", delta <= diffVal); LOG.info("******Done******"); } private float getExpectedCompressionRatio(JobConf simuJobConf, String RATIO_TYPE) { // Default decompression ratio is 0.50f irrespective of original //job compression ratio. if (simuJobConf.get(RATIO_TYPE) != null) { return Float.parseFloat(simuJobConf.get(RATIO_TYPE)); } else { return 0.50f; } } private float getActualCompressionRatio(long compressBytes, long uncompessBytes) { double ratio = ((double)compressBytes) / uncompessBytes; int significant = (int)Math.round(ratio * 100); return ((float)significant)/100; } /** * Verify the distributed cache files between the jobs in a gridmix run. * @param jobsInfo - jobConfs of simulated and original jobs as a map. */ public void verifyDistributedCacheBetweenJobs( Map<String,List<JobConf>> jobsInfo) { if (jobsInfo.size() > 1) { Map<String, Integer> simJobfilesOccurBtnJobs = getDistcacheFilesOccurenceBetweenJobs(jobsInfo, 0); Map<String, Integer> origJobfilesOccurBtnJobs = getDistcacheFilesOccurenceBetweenJobs(jobsInfo, 1); List<Integer> simuOccurList = getMapValuesAsList(simJobfilesOccurBtnJobs); Collections.sort(simuOccurList); List<Integer> origOccurList = getMapValuesAsList(origJobfilesOccurBtnJobs); Collections.sort(origOccurList); Assert.assertEquals("The unique count of distibuted cache files in " + "simulated jobs have not matched with the unique " + "count of original jobs distributed files ", simuOccurList.size(), origOccurList.size()); int index = 0; for (Integer origDistFileCount : origOccurList) { Assert.assertEquals("Distributed cache file reused in simulated " + "jobs has not matched with reused of distributed" + "cache file in original jobs.", origDistFileCount, simuOccurList.get(index)); index ++; } } } /** * Get the unique distributed cache files and occurrence between the jobs. * @param jobsInfo - job's configurations as a map. * @param jobConfIndex - 0 for simulated job configuration and * 1 for original jobs configuration. * @return - unique distributed cache files and occurrences as map. */ private Map<String, Integer> getDistcacheFilesOccurenceBetweenJobs( Map<String, List<JobConf>> jobsInfo, int jobConfIndex) { Map<String,Integer> filesOccurBtnJobs = new HashMap <String,Integer>(); Set<String> jobIds = jobsInfo.keySet(); Iterator<String > ite = jobIds.iterator(); while (ite.hasNext()) { String jobId = ite.next(); List<JobConf> jobconfs = jobsInfo.get(jobId); String [] distCacheFiles = jobconfs.get(jobConfIndex).get( GridMixConfig.GRIDMIX_DISTCACHE_FILES).split(","); String [] distCacheFileTimeStamps = jobconfs.get(jobConfIndex).get( GridMixConfig.GRIDMIX_DISTCACHE_TIMESTAMP).split(","); String [] distCacheFileVisib = jobconfs.get(jobConfIndex).get( GridMixConfig.GRIDMIX_DISTCACHE_VISIBILITIES).split(","); int indx = 0; for (String distCacheFile : distCacheFiles) { String fileAndSize = distCacheFile + "^" + distCacheFileTimeStamps[indx] + "^" + jobconfs.get(jobConfIndex).getUser(); if (filesOccurBtnJobs.get(fileAndSize) != null) { int count = filesOccurBtnJobs.get(fileAndSize); count ++; filesOccurBtnJobs.put(fileAndSize, count); } else { filesOccurBtnJobs.put(fileAndSize, 1); } } } return filesOccurBtnJobs; } /** * It verifies the distributed cache emulation of a job. * @param zombieJob - Original job story. * @param simuJobConf - Simulated job configuration. */ public void verifyDistributeCache(ZombieJob zombieJob, JobConf simuJobConf) throws IOException { if (simuJobConf.getBoolean(GridMixConfig.GRIDMIX_DISTCACHE_ENABLE, false)) { JobConf origJobConf = zombieJob.getJobConf(); assertFileVisibility(simuJobConf); assertDistcacheFiles(simuJobConf,origJobConf); assertFileSizes(simuJobConf,origJobConf); assertFileStamps(simuJobConf,origJobConf); } else { Assert.assertNull("Configuration has distributed cache visibilites" + "without enabled distributed cache emulation.", simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_VISIBILITIES)); Assert.assertNull("Configuration has distributed cache files time " + "stamps without enabled distributed cache emulation.", simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_TIMESTAMP)); Assert.assertNull("Configuration has distributed cache files paths" + "without enabled distributed cache emulation.", simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_FILES)); Assert.assertNull("Configuration has distributed cache files sizes" + "without enabled distributed cache emulation.", simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_FILESSIZE)); } } private void assertFileStamps(JobConf simuJobConf, JobConf origJobConf) { //Verify simulated jobs against distributed cache files time stamps. String [] origDCFTS = origJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_TIMESTAMP).split(","); String [] simuDCFTS = simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_TIMESTAMP).split(","); for (int index = 0; index < origDCFTS.length; index++) { Assert.assertTrue("Invalid time stamps between original " +"and simulated job", Long.parseLong(origDCFTS[index]) < Long.parseLong(simuDCFTS[index])); } } private void assertFileVisibility(JobConf simuJobConf ) { // Verify simulated jobs against distributed cache files visibilities. String [] distFiles = simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_FILES).split(","); String [] simuDistVisibilities = simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_VISIBILITIES).split(","); List<Boolean> expFileVisibility = new ArrayList<Boolean >(); int index = 0; for (String distFile : distFiles) { boolean isLocalDistCache = GridmixSystemTestCase.isLocalDistCache( distFile, simuJobConf.getUser(), Boolean.valueOf(simuDistVisibilities[index])); if (!isLocalDistCache) { expFileVisibility.add(true); } else { expFileVisibility.add(false); } index ++; } index = 0; for (String actFileVisibility : simuDistVisibilities) { Assert.assertEquals("Simulated job distributed cache file " + "visibilities has not matched.", expFileVisibility.get(index), Boolean.valueOf(actFileVisibility)); index ++; } } private void assertDistcacheFiles(JobConf simuJobConf, JobConf origJobConf) throws IOException { //Verify simulated jobs against distributed cache files. String [] origDistFiles = origJobConf.get( GridMixConfig.GRIDMIX_DISTCACHE_FILES).split(","); String [] simuDistFiles = simuJobConf.get( GridMixConfig.GRIDMIX_DISTCACHE_FILES).split(","); String [] simuDistVisibilities = simuJobConf.get( GridMixConfig.GRIDMIX_DISTCACHE_VISIBILITIES).split(","); Assert.assertEquals("No. of simulatued job's distcache files mismacted" + "with no.of original job's distcache files", origDistFiles.length, simuDistFiles.length); int index = 0; for (String simDistFile : simuDistFiles) { Path distPath = new Path(simDistFile); boolean isLocalDistCache = GridmixSystemTestCase.isLocalDistCache(simDistFile, simuJobConf.getUser(), Boolean.valueOf(simuDistVisibilities[index])); if (!isLocalDistCache) { FileSystem fs = distPath.getFileSystem(conf); FileStatus fstat = fs.getFileStatus(distPath); FsPermission permission = fstat.getPermission(); Assert.assertTrue("HDFS distributed cache file has wrong " + "permissions for users.", FsAction.READ_WRITE.SYMBOL == permission.getUserAction().SYMBOL); Assert.assertTrue("HDFS distributed cache file has wrong " + "permissions for groups.", FsAction.READ.SYMBOL == permission.getGroupAction().SYMBOL); Assert.assertTrue("HDSFS distributed cache file has wrong " + "permissions for others.", FsAction.READ.SYMBOL == permission.getOtherAction().SYMBOL); } index++; } } private void assertFileSizes(JobConf simuJobConf, JobConf origJobConf) { // Verify simulated jobs against distributed cache files size. List<String> origDistFilesSize = Arrays.asList(origJobConf.get( GridMixConfig.GRIDMIX_DISTCACHE_FILESSIZE).split(",")); Collections.sort(origDistFilesSize); List<String> simuDistFilesSize = Arrays.asList(simuJobConf.get( GridMixConfig.GRIDMIX_DISTCACHE_FILESSIZE).split(",")); Collections.sort(simuDistFilesSize); Assert.assertEquals("Simulated job's file size list has not " + "matched with the Original job's file size list.", origDistFilesSize.size(), simuDistFilesSize.size()); for (int index = 0; index < origDistFilesSize.size(); index ++) { Assert.assertEquals("Simulated job distcache file size has not " + "matched with original job distcache file size.", origDistFilesSize.get(index), simuDistFilesSize.get(index)); } } private void setJobDistributedCacheInfo(String jobId, JobConf simuJobConf, JobConf origJobConf) { if (simuJobConf.get(GridMixConfig.GRIDMIX_DISTCACHE_FILES) != null) { List<JobConf> jobConfs = new ArrayList<JobConf>(); jobConfs.add(simuJobConf); jobConfs.add(origJobConf); simuAndOrigJobsInfo.put(jobId,jobConfs); } } private List<Integer> getMapValuesAsList(Map<String,Integer> jobOccurs) { List<Integer> occursList = new ArrayList<Integer>(); Set<String> files = jobOccurs.keySet(); Iterator<String > ite = files.iterator(); while (ite.hasNext()) { String file = ite.next(); occursList.add(jobOccurs.get(file)); } return occursList; } /** * It verifies the high ram gridmix jobs. * @param zombieJob - Original job story. * @param simuJobConf - Simulated job configuration. */ @SuppressWarnings("deprecation") public void verifyHighRamMemoryJobs(ZombieJob zombieJob, JobConf simuJobConf) { JobConf origJobConf = zombieJob.getJobConf(); int origMapFactor = getMapFactor(origJobConf); int origReduceFactor = getReduceFactor(origJobConf); boolean isHighRamEnable = simuJobConf.getBoolean(GridMixConfig.GRIDMIX_HIGH_RAM_JOB_ENABLE, false); if (isHighRamEnable) { if (origMapFactor >= 2 && origReduceFactor >= 2) { assertGridMixHighRamJob(simuJobConf, origJobConf, 1); } else if(origMapFactor >= 2) { assertGridMixHighRamJob(simuJobConf, origJobConf, 2); } else if(origReduceFactor >= 2) { assertGridMixHighRamJob(simuJobConf, origJobConf, 3); } } else { if (origMapFactor >= 2 && origReduceFactor >= 2) { assertGridMixHighRamJob(simuJobConf, origJobConf, 4); } else if(origMapFactor >= 2) { assertGridMixHighRamJob(simuJobConf, origJobConf, 5); } else if(origReduceFactor >= 2) { assertGridMixHighRamJob(simuJobConf, origJobConf, 6); } } } /** * Get the value for identifying the slots used by the map. * @param jobConf - job configuration * @return - map factor value. */ public static int getMapFactor(Configuration jobConf) { long clusterMapMem = Long.parseLong(jobConf.get(GridMixConfig.CLUSTER_MAP_MEMORY)); long jobMapMem = Long.parseLong(jobConf.get(GridMixConfig.JOB_MAP_MEMORY_MB)); return (int)Math.ceil((double)jobMapMem / clusterMapMem); } /** * Get the value for identifying the slots used by the reduce. * @param jobConf - job configuration. * @return - reduce factor value. */ public static int getReduceFactor(Configuration jobConf) { long clusterReduceMem = Long.parseLong(jobConf.get(GridMixConfig.CLUSTER_REDUCE_MEMORY)); long jobReduceMem = Long.parseLong(jobConf.get(GridMixConfig.JOB_REDUCE_MEMORY_MB)); return (int)Math.ceil((double)jobReduceMem / clusterReduceMem); } @SuppressWarnings("deprecation") private void assertGridMixHighRamJob(JobConf simuJobConf, Configuration origConf, int option) { int simuMapFactor = getMapFactor(simuJobConf); int simuReduceFactor = getReduceFactor(simuJobConf); /** * option 1 : Both map and reduce honors the high ram. * option 2 : Map only honors the high ram. * option 3 : Reduce only honors the high ram. * option 4 : Both map and reduce should not honors the high ram * in disable state. * option 5 : Map should not honors the high ram in disable state. * option 6 : Reduce should not honors the high ram in disable state. */ switch (option) { case 1 : Assert.assertTrue("Gridmix job has not honored the high " + "ram for map.", simuMapFactor >= 2 && simuMapFactor == getMapFactor(origConf)); Assert.assertTrue("Gridmix job has not honored the high " + "ram for reduce.", simuReduceFactor >= 2 && simuReduceFactor == getReduceFactor(origConf)); break; case 2 : Assert.assertTrue("Gridmix job has not honored the high " + "ram for map.", simuMapFactor >= 2 && simuMapFactor == getMapFactor(origConf)); break; case 3 : Assert.assertTrue("Girdmix job has not honored the high " + "ram for reduce.", simuReduceFactor >= 2 && simuReduceFactor == getReduceFactor(origConf)); break; case 4 : Assert.assertTrue("Gridmix job has honored the high " + "ram for map in emulation disable state.", simuMapFactor < 2 && simuMapFactor != getMapFactor(origConf)); Assert.assertTrue("Gridmix job has honored the high " + "ram for reduce in emulation disable state.", simuReduceFactor < 2 && simuReduceFactor != getReduceFactor(origConf)); break; case 5 : Assert.assertTrue("Gridmix job has honored the high " + "ram for map in emulation disable state.", simuMapFactor < 2 && simuMapFactor != getMapFactor(origConf)); break; case 6 : Assert.assertTrue("Girdmix job has honored the high " + "ram for reduce in emulation disable state.", simuReduceFactor < 2 && simuReduceFactor != getReduceFactor(origConf)); break; } } /** * Get task memory after scaling based on cluster configuration. * @param jobTaskKey - Job task key attribute. * @param clusterTaskKey - Cluster task key attribute. * @param origConf - Original job configuration. * @param simuConf - Simulated job configuration. * @return scaled task memory value. */ @SuppressWarnings("deprecation") public static long getScaledTaskMemInMB(String jobTaskKey, String clusterTaskKey, Configuration origConf, Configuration simuConf) { long simuClusterTaskValue = simuConf.getLong(clusterTaskKey, JobConf.DISABLED_MEMORY_LIMIT); long origClusterTaskValue = origConf.getLong(clusterTaskKey, JobConf.DISABLED_MEMORY_LIMIT); long origJobTaskValue = origConf.getLong(jobTaskKey, JobConf.DISABLED_MEMORY_LIMIT); double scaleFactor = Math.ceil((double)origJobTaskValue / origClusterTaskValue); long simulatedJobValue = (long)(scaleFactor * simuClusterTaskValue); return simulatedJobValue; } /** * It Verifies the memory limit of a task. * @param TaskMemInMB - task memory limit. * @param taskLimitInMB - task upper limit. */ public static void verifyMemoryLimits(long TaskMemInMB, long taskLimitInMB) { if (TaskMemInMB > taskLimitInMB) { Assert.fail("Simulated job's task memory exceeds the " + "upper limit of task virtual memory."); } } private String convertJobStatus(String jobStatus) { if (jobStatus.equals("SUCCEEDED")) { return "SUCCESS"; } else { return jobStatus; } } private String convertBytes(long bytesValue) { int units = 1024; if( bytesValue < units ) { return String.valueOf(bytesValue)+ "B"; } else { // it converts the bytes into either KB or MB or GB or TB etc. int exp = (int)(Math.log(bytesValue) / Math.log(units)); return String.format("%1d%sB",(long)(bytesValue / Math.pow(units, exp)), "KMGTPE".charAt(exp -1)); } } private long getCounterValue(Counters counters, String key) throws ParseException { for (String groupName : counters.getGroupNames()) { Group totalGroup = counters.getGroup(groupName); Iterator<Counter> itrCounter = totalGroup.iterator(); while (itrCounter.hasNext()) { Counter counter = itrCounter.next(); if (counter.getName().equals(key)) { return counter.getValue(); } } } return 0; } }